<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"><title>六、Nginx 基本数据结构 | 云少IT</title><meta name="keywords" content="云少IT,IT,技术,分享,程序员,博客,教程,工具,框架,bug,java,spring,数据库,"><meta name="author" content="云少"><meta name="copyright" content="云少"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-touch-fullscreen" content="yes"><meta name="apple-mobile-web-app-title" content="六、Nginx 基本数据结构"><meta name="application-name" content="六、Nginx 基本数据结构"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="#ffffff"><meta property="og:type" content="article"><meta property="og:title" content="六、Nginx 基本数据结构"><meta property="og:url" content="https://it985.github.io/posts/c504cd62.html"><meta property="og:site_name" content="云少IT"><meta property="og:description" content="六、Nginx 基本数据结构Nginx 的作者为追求极致的高效，自己实现了很多颇具特色的 Nginx 风格的数据结构以及公共函数。比如，Nginx 提供了带长度的字符串，根据编译器选项优化过的字符串拷贝函数 ngx_copy 等。所以，在我们写 Nginx 模块时，应该尽量调用 Nginx 提供的"><meta property="og:locale" content="zh-CN"><meta property="og:image" content="https://img.xjh.me/random_img.php?type=bg&amp;ctype=nature&amp;return=302&amp;_r_=b4b7cfea-fdef-8d0f-cb5d-61fdc2167c9f"><meta property="article:author" content="云少"><meta property="article:tag" content="云少IT,IT,技术,分享,程序员,博客,教程,工具,框架,bug,java,spring,数据库,"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://img.xjh.me/random_img.php?type=bg&amp;ctype=nature&amp;return=302&amp;_r_=b4b7cfea-fdef-8d0f-cb5d-61fdc2167c9f"><meta name="description" content="六、Nginx 基本数据结构Nginx 的作者为追求极致的高效，自己实现了很多颇具特色的 Nginx 风格的数据结构以及公共函数。比如，Nginx 提供了带长度的字符串，根据编译器选项优化过的字符串拷贝函数 ngx_copy 等。所以，在我们写 Nginx 模块时，应该尽量调用 Nginx 提供的"><link rel="shortcut icon" href="/img/logo.webp"><link rel="canonical" href="https://it985.github.io/posts/c504cd62"><link rel="preconnect" href="//npm.elemecdn.com"><link rel="preconnect" href="//npm.onmicrosoft.cn"><link rel="preconnect" href="//www.google-analytics.com" crossorigin=""><link rel="preconnect" href="//busuanzi.ibruce.info"><meta name="google-site-verification" content="NuBZ4r-QCqSgo4XUScdEsQW0bolIHEiVGq4A16ndPQA"><meta name="baidu-site-verification" content="code-xxx"><meta name="msvalidate.01" content="xxx"><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.cbd.int/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/swiper/swiper.min.css" media="print" onload='this.media="all"'><script async src="https://www.googletagmanager.com/gtag/js?id=G-3VMKW5TZBM"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-3VMKW5TZBM")</script><script>const GLOBAL_CONFIG={linkPageTop:{enable:!0,title:"与数百名博主无限进步",addFriendPlaceholder:"昵称（请勿包含博客等字样）：\n网站地址（要求博客地址，请勿提交个人主页）：\n头像图片url（请提供尽可能清晰的图片，我会上传到我自己的图床）：\n描述：\n站点截图（可选）：\n"},peoplecanvas:void 0,postHeadAiDescription:{enable:!0,gptName:"云AI",mode:"tianli",switchBtn:!1,btnLink:"https://afdian.net/item/886a79d4db6711eda42a52540025c377",randomNum:3,basicWordCount:1e3,key:"48580d1e3f53ae174a1e",Referer:"https://blog.tryrun.top"},diytitle:{enable:!0,leaveTitle:"w(ﾟДﾟ)w 不要走！再看看嘛！",backTitle:"♪(^∇^*)欢迎肥来！"},LA51:{enable:!0,ck:"JiFOrFoQklEn9YLS",LingQueMonitorID:"JiqlTmdeI4e1fPbd"},greetingBox:{enable:!0,default:"晚上好👋",list:[{greeting:"晚安😴",startTime:0,endTime:5},{greeting:"早上好鸭👋, 祝你一天好心情！",startTime:6,endTime:9},{greeting:"上午好👋, 状态很好，鼓励一下～",startTime:10,endTime:10},{greeting:"11点多啦, 在坚持一下就吃饭啦～",startTime:11,endTime:11},{greeting:"午安👋, 宝贝",startTime:12,endTime:14},{greeting:"🌈充实的一天辛苦啦！",startTime:14,endTime:18},{greeting:"19点喽, 奖励一顿丰盛的大餐吧🍔。",startTime:19,endTime:19},{greeting:"晚上好👋, 在属于自己的时间好好放松😌~",startTime:20,endTime:24}]},twikooEnvId:"https://twikoo.tryrun.top/",commentBarrageConfig:void 0,root:"/",preloader:{source:2},friends_vue_info:{apiurl:"https://friends.tryrun.top/"},navMusic:!1,mainTone:void 0,authorStatus:{skills:["🤖️ 数码科技爱好者","🔍 分享与热心帮助","🏠 智能家居小能手","🔨 设计开发一条龙","🤝 专修交互与设计","🏃 脚踏实地行动派","🧱 团队小组发动机","💢 壮汉人狠话不多"]},algolia:{appId:"T5VW6VDYLS",apiKey:"227bcb041816af13cb1698db15a8ac89",indexName:"hexo-blog",hits:{per_page:6},languages:{input_placeholder:"输入关键词后按下回车查找",hits_empty:"找不到您查询的内容：${query}",hits_stats:"找到 ${hits} 条结果，用时 ${time} 毫秒"}},localSearch:void 0,translate:{defaultEncoding:2,translateDelay:0,msgToTraditionalChinese:"繁",msgToSimplifiedChinese:"简",rightMenuMsgToTraditionalChinese:"转为繁体",rightMenuMsgToSimplifiedChinese:"转为简体"},noticeOutdate:{limitDay:365,position:"top",messagePrev:"It has been",messageNext:"days since the last update, the content of the article may be outdated."},highlight:{plugin:"highlighjs",highlightCopy:!0,highlightLang:!0,highlightHeightLimit:330},copy:{success:"复制成功",error:"复制错误",noSupport:"浏览器不支持"},relativeDate:{homepage:!0,simplehomepage:!1,post:!0},runtime:"天",date_suffix:{just:"刚刚",min:"分钟前",hour:"小时前",day:"天前",month:"个月前"},copyright:void 0,lightbox:"fancybox",Snackbar:{chs_to_cht:"你已切换为繁体",cht_to_chs:"你已切换为简体",day_to_night:"你已切换为深色模式",night_to_day:"你已切换为浅色模式",bgLight:"#425AEF",bgDark:"#1f1f1f",position:"top-center"},source:{justifiedGallery:{js:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.min.js",css:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.css"}},isPhotoFigcaption:!1,islazyload:!0,isAnchor:!1,shortcutKey:void 0,autoDarkmode:!0}</script><script id="config-diff">var GLOBAL_CONFIG_SITE={configTitle:"云少IT",title:"六、Nginx 基本数据结构",postAI:"true",pageFillDescription:"六、Nginx 基本数据结构, ngx_str_t, ngx_pool_t, ngx_array_t, ngx_hash_t, ngx_hash_wildcard_t, ngx_hash_combined_t, ngx_hash_keys_arrays_t, ngx_chain_t, ngx_buf_t, ngx_list_t, ngx_queue_t六基本数据结构的作者为追求极致的高效自己实现了很多颇具特色的风格的数据结构以及公共函数比如提供了带长度的字符串根据编译器选项优化过的字符串拷贝函数等所以在我们写模块时应该尽量调用提供的尽管有些只是对的宏定义本节我们介绍等一系列最基本的数据结构及相关的使用技巧以及注意事项在源码目录的下面的里面包含了字符串的封装以及字符串相关操作的提供了一个带长度的字符串结构它的原型如下在结构体当中指向字符串数据的第一个字符字符串的结束用长度来表示而不是由来表示结束所以在写代码时处理字符串的方法跟我们平时使用有很大的不一样但要时刻记住字符串不以结束尽量使用提供的字符串操作的来操作字符串那么这样做有什么好处呢首先通过长度来表示字符串长度减少计算字符串长度的次数其次可以重复引用一段字符串内存可以指向任意内存长度表示结束而不用去一份自己的字符串因为如果要以结束而不能更改原字符串所以势必要一段字符串我们在结构体的成员中可以找到很多字符串引用一段内存的例子比如等等这些字符串的部分都是指向在接收数据时创建所指向的内存中就没有必要一份出来这样的话减少了很多不必要的内存分配与拷贝正是基于此特性在中必须谨慎的去修改一个字符串在修改字符串时需要认真的去考虑是否可以修改该字符串字符串修改后是否会对其它的引用造成影响在后面介绍函数的时候就会看到这一点但是使用的字符串会产生一些问题提供的很多系统函数大多是通过来表示字符串的结束所以我们在调用系统时就不能直接传入了此时通常的做法是创建一段大小的内存然后字符串最后一个字节置为比较的做法是将字符串最后一个字符的后一个字符一个然后设置为在做完调用后再由改回来但前提条件是你得确定这个字符是可以修改的而且是有内存分配不会越界但一般不建议这么做接下来看看提供的操作字符串相关的是一个宏它通过一个以结尾的普通字符串构造一个的字符串鉴于其中采用操作符计算字符串长度因此参数必须是一个常量字符串定义变量时使用初始化字符串为空字符串符串的长度为为用于设置字符串为由于使用计算长度故必须为常量字符串用于设置字符串为空串长度为为上面这四个函数使用时一定要小心与是格式的故只能用于赋值时初始化如如果向下面这样使用就会有问题这里涉及到语言中对结构体变量赋值操作的语法规则在此不做介绍编译出错编译出错这种情况可以调用与这两个函数来做按照标准您也可以这么做另外要注意的是与在调用时传进去的字符串一定是常量字符串否则会得到意想不到的错误因为内部使用了如果传入的是那么计算的是这个指针的长度而不是字符串的长度如问题产生此外值得注意的是由于与实际上是两行语句故在等语句中单独使用需要用花括号括起来例如问题产生问题产生将的前个字符转换成小写存放在字符串当中调用者需要保证指向的空间大于等于且指向的空间必须可写操作不会对原字符串产生变动如要更改原字符串可以区分大小写的字符串比较只比较前个字符区分大小写的不带长度的字符串比较不区分大小写的不带长度的字符串比较不区分大小写的带长度的字符串比较只比较前个字符上面这三个函数用于字符串格式化的第二个参数指明的空间大小则通过来指明空间的大小推荐使用第二个或第三个函数来格式化字符串函数还是比较危险的容易产生缓冲区溢出漏洞在这一系列函数中在兼容中格式化字符串的形式之外还添加了一些方便格式化类型的一些转义字符比如用于格式化结构在源文件的中有说明这里特别要提醒的是我们最常用于格式化结构其对应的转义符是传给函数的一定要是指针类型否则程序就会掉这也是我们最容易犯的错比如注意取地址这两个函数用于对进行编码与解码调用前需要保证中有足够的空间来存放结果如果不知道具体大小可先调用与来预估最大占用空间对进行编码根据来按不同的方式进行编码如果为则返回需要转义的字符的数量由此可得到需要的空间大小的类型可以是对进行反编码可以是这三个值如果是则表示中的所有字符都要进行转码如果是与则遇到后就结束了后面的字符就不管了而与之间的区别是对于遇到的需要转码的字符都会转码而则只会对非可见字符进行转码对标签进行编码当然我这里只介绍了一些常用的的使用大家可以先熟悉一下在实际使用过程中遇到不明白的最快最直接的方法就是去看源码看的实现或看自身调用的地方是怎么做的代码就是最好的文档是一个非常重要的数据结构在很多重要的场合都有使用很多重要的数据结构也都在使用它那么它究竟是一个什么东西呢简单的说它提供了一种机制帮助管理一系列的资源如内存文件等使得对这些资源的使用和释放统一进行免除了使用过程中考虑到对各种各样资源的什么时候释放是否遗漏了释放的担心例如对于内存的管理如果我们需要使用内存那么总是从一个的对象中获取内存在最终的某个时刻我们销毁这个对象所有这些内存都被释放了这样我们就不必要对对这些内存进行和的操作不用担心是否某块被出来的内存没有被释放因为当对象被销毁的时候所有从这个对象中分配出来的内存都会被统一释放掉再比如我们要使用一系列的文件但是我们打开以后最终需要都关闭那么我们就把这些文件统一登记到一个对象中当这个对象被销毁的时候所有这些文件都将会被关闭从上面举的两个例子中我们可以看出使用这个数据结构的时候所有的资源的释放都在这个对象被销毁的时刻统一进行了释放那么就会带来一个问题就是这些资源的生存周期或者说被占用的时间是跟的生存周期基本一致也提供了少量操作可以提前释放资源从最高效的角度来说这并不是最好的比如我们需要依次使用三个资源且使用完的时候就不会再被使用了使用的时候和都不会被使用到如果不使用来管理这三个资源那我们可能从系统里面申请使用然后在释放接着申请使用再释放最后申请使用然后释放但是当我们使用一个对象来管理这三个资源的时候和的释放是在最后一起发生的也就是在使用完以后诚然这在客观上增加了程序在一段时间的资源使用量但是这也减轻了程序员分别管理三个资源的生命周期的工作这也就是有所得必有所失的道理实际上是一个取舍的问题要看在具体的情况下你更在乎的是哪个可以看一下在里面一个典型的使用的场景对于处理的每个会生成一个对象与这个关联所有处理过程中需要申请的资源都从这个对象中获取当这个处理完成以后所有在处理过程中申请的资源都将随着这个关联的对象的销毁而释放相关结构及操作被定义在文件中从的一般使用者的角度来说可不用关注结构中各字段作用所以这里也不会进行详细的解释当然在说明某些操作函数的使用的时候如有必要会进行说明下面我们来分别解释下的相关操作创建一个初始节点大小为的为后续在该上进行操作时输出日志的对象需要说明的是的选择的大小必须小于等于且必须大于选择大于的值会造成浪费因为大于该限制的空间不会被用到只是说在第一个由对象管理的内存块上的内存后续的分配如果第一个内存块上的空闲部分已用完会再分配的选择小于的值会造成程序崩溃由于初始大小的内存块中要用一部分来存储这个信息本身当一个对象被创建以后该对象的字段被赋值为和这两者中比较小的后续的从这个中分配的内存块在第一块内存使用完成以后如果要继续分配的话就需要继续从操作系统申请内存当内存的大小小于等于字段的时候则分配新的内存块链接在这个字段实际上是字段管理的一条链表上当要分配的内存块是比大的那么从系统中申请的内存是被挂接在字段管理的一条链表上我们暂且把这个称之为大块内存链和小块内存链从这个中分配一块为大小的内存注意此函数分配的内存的起始地址按照进行了对齐对齐操作会提高系统处理的速度但会造成少量内存的浪费从这个中分配一块为大小的内存但是此函数分配的内存并没有像上面的函数那样进行过对齐该函数也是分配大小的内存并且对分配的内存块进行了清零内部实际上是转调用实现的按照指定对齐大小来申请一块大小为的内存此处获取的内存不管大小都将被置于大内存块链中管理对于被置于大块内存链也就是被字段管理的一列内存中的某块进行释放该函数的实现是顺序遍历管理的大块内存链表所以效率比较低下如果在这个链表中找到了这块内存则释放并返回否则返回由于这个操作效率比较低下除非必要也就是说这块内存非常大确应及时释放否则一般不需要调用反正内存在这个被销毁的时候总归会都释放掉的嘛中的字段管理着一个特殊的链表该链表的每一项都记录着一个特殊的需要释放的资源对于这个链表中每个节点所包含的资源如何去释放是自说明的这也就提供了非常大的灵活性意味着不仅仅可以管理内存通过这个机制也可以管理任何需要释放的资源例如关闭文件或者删除文件等等下面我们看一下这个链表每个节点的类型指明了该节点所对应的资源是一个函数指针指向一个可以释放所对应资源的函数该函数只有一个参数就是指向该链表中下一个元素看到这里这个函数的用法我相信大家都应该有一些明白了但是这个参数是起什么作用的呢这个就是要存储这个字段所指向的资源的大小该函数会为分配大小的空间比如我们需要最后删除一个文件那我们在调用这个函数的时候把指定为存储文件名的字符串的大小然后调用这个函数给链表中增加一项该函数会返回新添加的这个节点我们然后把这个节点中的字段拷贝为文件名把字段赋值为一个删除文件的函数当然该函数的原型要按照该函数就是释放中持有的所有内存以及依次调用字段所管理的链表中每个元素的字段所指向的函数来释放掉所有该管理的资源并且把指向的也释放掉了完全不可用了该函数释放中所有大块内存链表上的内存小块内存链上的内存块都修改为可用但是不会去处理链表上的项目是内部使用的数组结构的数组结构在存储上与大家认知的语言内置的数组有相似性比如实际上存储数据的区域也是一大块连续的内存但是数组除了存储数据的内存以外还包含一些元信息来描述相关的一些信息下面我们从数组的定义上来详细的了解一下的定义位于里面指向实际的数据存储区域数组实际元素个数数组单个元素的大小单位是字节数组的容量表示该数组在不引发扩容的前提下可以最多存储的元素的个数当增长到达时如果再往此数组中存储元素则会引发数组的扩容数组的容量将会扩展到原有容量的倍大小实际上是分配新的一块内存新的一块内存的大小是原有内存大小的倍原有的数据会被拷贝到新的一块内存中该数组用来分配内存的内存池下面介绍相关操作函数创建一个新的数组对象并返回这个对象数组分配内存使用的内存池数组的初始容量大小即在不扩容的情况下最多可以容纳的元素个数单个元素的大小单位是字节销毁该数组对象并释放其分配的内存回内存池在数组上新追加一个元素并返回指向新元素的指针需要把返回的指针使用类型转换转换为具体的类型然后再给新元素本身或者是各字段如果数组的元素是复杂类型赋值在数组上追加个元素并返回指向这些追加元素的首个元素的位置的指针如果一个数组对象是被分配在堆上的那么当调用销毁以后如果想再次使用就可以调用此函数如果一个数组对象是被分配在栈上的那么就需要调用此函数进行初始化的工作以后才可以使用注意事项由于使用分配内存数组在扩容时旧的内存不会被释放会造成内存的浪费因此最好能提前规划好数组的容量在创建或者初始化的时候一次搞定避免多次扩容造成内存浪费是自己的表的实现定义和实现位于中的实现也与数据结构教科书上所描述的表的实现是大同小异对于常用的解决冲突的方法有线性探测二次探测和开链法等使用的是最常用的一种也就是开链法这也是中的表使用的方法但是的实现又有其几个显著的特点不像其他的表的实现可以插入删除元素它只能一次初始化就构建起整个表以后既不能再删除也不能在插入元素了的开链并不是真的开了一个链表实际上是开了一段连续的存储空间几乎可以看做是一个数组这是因为在初始化的时候会经历一次预计算的过程提前把每个桶里面会有多少元素放进去给计算出来这样就提前知道每个桶的大小了那么就不需要使用链表一段连续的存储空间就足够了这也从一定程度上节省了内存的使用从上面的描述我们可以看出来这个值越大越造成内存的浪费就两步首先是初始化然后就可以在里面进行查找了下面我们详细来看一下的初始化首先我们来看一下初始化函数该函数的第一个参数是初始化的一些参数的一个集合是初始化一个所需要的所有的一个数组而就是的个数下面先看一下类型该类型提供了初始化一个表所需要的一些基本信息该字段如果为那么调用完初始化函数后该字段指向新创建出来的表如果该字段不为那么在初始的时候所有的数据被插入了这个字段所指的表中指向从字符串生成值的函数的源代码中提供了默认的实现函数表中的桶的个数该字段越大元素存储时冲突的可能性越小每个桶中存储的元素会更少则查询起来的速度更快当然这个值越大越造成内存的浪费也越大实际上也浪费不了多少每个桶的最大限制大小单位是字节如果在初始化一个表的时候发现某个桶里面无法存的下所有属于该桶的元素则表初始化失败该表的名字该表分配内存使用的该表使用的临时在初始化完成以后该可以被释放和销毁掉下面来看一下存储表的数组的结构和的含义显而易见就不用解释了是对使用函数计算出来的值对这两个结构分析完成以后我想大家应该都已经明白这个函数应该是如何使用了吧该函数成功初始化一个表以后返回否则返回在里面查找对应的实际上这里的是对真正的也就是计算出的值是的长度如果查找成功则返回指向的指针否则返回为了处理带有通配符的域名的匹配问题实现了这样的表他可以支持两种类型的带有通配符的域名一种是通配符在前的例如也可以省略掉星号直接写成这样的可以匹配之类的另外一种是通配符在末尾的例如请特别注意通配符在末尾的不像位于开始的通配符可以被省略掉这样的通配符可以匹配之类的域名有一点必须说明就是一个类型的表只能包含通配符在前的或者是通配符在后的不能同时包含两种类型的通配符的类型变量的构建是通过函数完成的而查询是通过函数或者来做的查询包含通配符在前的的表的而是查询包含通配符在后的的表的下面详细说明这几个函数的用法该函数用来构建一个可以包含通配符的表构造一个通配符表的一些参数的一个集合关于该参数对应的类型的说明请参见类型中函数的说明构造此表的所有的通配符的数组特别要注意的是这里的已经都是被预处理过的例如或者被预处理完成以后变成了而则被预处理为为什么会被处理这样这里不得不简单地描述一下通配符表的实现原理当构造此类型的表的时候实际上是构造了一个表的一个链表是通过表中的链接起来的比如对于将会构造出个表第一个表中有一个为的表项该表项的包含有指向第二个表的指针而第二个表中有一个表项该表项的包含有指向对应的的指针那么查询的时候比如查询的时候先查通过查可以找到第二级的表在第二级表中再查找依次类推直到在某一级的表中查到的表项对应的对应一个真正的值而非一个指向下一级表的指针的时候查询过程结束这里有一点需要特别注意的就是数组中元素的值低两位必须为有特殊用途如果不满足这个条件这个表查询不出正确结果数组元素的个数该函数执行成功返回否则该函数查询包含通配符在前的的表的表对象的指针需要查询的域名例如的长度该函数返回匹配的通配符对应如果没有查到返回该函数查询包含通配符在末尾的的表的参数及返回值请参加上个函数的说明组合类型表该表的定义如下从其定义显见该类型实际上包含了三个表一个普通表一个包含前向通配符的表和一个包含后向通配符的表提供该类型的作用在于提供一个方便的容器包含三个类型的表当有包含通配符的和不包含通配符的一组构建表以后以一种方便的方式来查询你不需要再考虑一个到底是应该到哪个类型的表里去查了构造这样一组合表的时候首先定义一个该类型的变量再分别构造其包含的三个子表即可对于该类型表的查询提供了一个方便的函数该函数在此组合表中依次查询其三个子表看是否匹配一旦找到立即返回查找结果也就是说如果有多个可能匹配则只返回第一个匹配的结果此组合表对象根据计算出的值的具体内容的长度返回查询的结果未查到则返回大家看到在构建一个的时候需要对通配符的哪些进行预处理这个处理起来比较麻烦而当有一组这些里面既有无通配符的也有包含通配符的的时候我们就需要构建三个表一个包含普通的的表一个包含前向通配符的表一个包含后向通配符的表或者也可以把这三个表组合成一个在这种情况下为了让大家方便的构造这些表提供给了此辅助类型该类型以及相关的操作函数也定义在里我们先来看一下该类型的定义将要构建的表的桶的个数对于使用这个结构中包含的信息构建的三种类型的表都会使用此参数构建这些表使用的在构建这个类型以及最终的三个表过程中可能用到临时该可以在构建完成以后被销毁掉这里只是存放临时的一些内存消耗存放所有非通配符的数组这是个二维数组第一个维度代表的是的编号那么中存放的是所有的算出来的值对取模以后的值为的假设有个分别是和假设值算出来以后对取模的值都是那么这三个的值就顺序存放在该值在调用的过程中用来保存和检测是否有冲突的值也就是是否有重复放前向通配符被处理完成以后的值比如被处理完成以后变成被存放在此数组中存放后向通配符被处理完成以后的值比如被处理完成以后变成被存放在此数组中该值在调用的过程中用来保存和检测是否有冲突的前向通配符的值也就是是否有重复该值在调用的过程中用来保存和检测是否有冲突的后向通配符的值也就是是否有重复在定义一个这个类型的变量并对字段和赋值以后就可以调用函数把所有的加入到这个结构中了该函数会自动实现普通带前向通配符的和带后向通配符的的分类和检查并将这个些值存放到对应的字段中去然后就可以通过检查这个结构体中的三个数组是否为空来决定是否构建普通表前向通配符表和后向通配符表了在构建这三个类型的表的时候可以分别使用三个数组构建出这三个表以后可以组合在一个对象中使用进行查找或者是仍然保持三个独立的变量对应这三个表自己决定何时以及在哪个表中进行查询初始化这个结构主要是对这个结构中的类型的字段进行初始化成功返回该结构的对象指针该字段有个值可选择即和用来指明将要建立的表的类型如果是则有比较小的桶的个数和数组元素大小则相反一般是循环调用这个函数把一组键值对加入到这个结构体中返回是加入成功返回意味着值重复该结构的对象指针参数名自解释了参数名自解释了有两个标志位可以设置和同时要设置的使用逻辑与操作符就可以了被设置的时候在计算值的时候的值不会被转成小写字符否则会被设置的时候说明里面可能含有通配符会进行相应的处理如果两个标志位都不设置传有关于这个数据结构的使用可以参考中的函数的模块在处理从别的模块或者是模块传递过来的数据实际上就是需要发送给客户端的这个传递过来的数据是以一个链表的形式而且数据可能被分多次传递过来也就是多次调用的处理函数以不同的该结构被定义在下面我们来看一下的定义就个字段指向这个链表的下个节点指向实际的数据所以在这个链表上追加节点也是非常容易只要把末尾元素的指针指向新的节点把新节点的赋值为即可该函数创建一个的对象并返回指向对象的指针失败返回该宏释放一个类型的对象如果要释放整个则迭代此链表对每个节点使用此宏即可注意对类型的释放并不是真的释放了内存而仅仅是把这个对象挂在了这个对象的一个叫做的字段对应的上以供下次从这个上分配类型对象的时候快速的从这个上取下链首元素就返回了当然如果这个链是空的才会真的在这个上使用函数进行分配这个就是这个链表的每个节点的实际数据该结构实际上是一种抽象的数据结构它代表某种具体的数据这个数据可能是指向内存中的某个缓冲区也可能指向一个文件的某一部分也可能是一些纯元数据元数据的作用在于指示这个链表的读取者对读取的数据进行不同的处理该数据结构位于文件中我们来看一下它的定义当所指向的数据在内存里的时候指向的是这段数据开始的位置当所指向的数据在内存里的时候指向的是这段数据结束的位置当所指向的数据是在文件里的时候指向的是这段数据的开始位置在文件中的偏移量当所指向的数据是在文件里的时候指向的是这段数据的结束位置在文件中的偏移量当所指向的数据在内存里的时候这一整块内存包含的内容可能被包含在多个中比如在某段数据中间插入了其他的数据这一块数据就需要被拆分开那么这些中的和都指向这一块内存的开始地址和结束地址而和指向本所实际包含的数据的开始和结尾解释参见实际上是一个类型的指针使用者可以关联任意的对象上去只要对使用者有意义当所包含的内容在文件中时字段指向对应的文件对象当这个完整了另外一个的所有字段的时候那么这两个指向的实际上是同一块内存或者是同一个文件的同一部分此时这两个的字段都是指向对方的那么对于这样的两个在释放的时候就需要使用者特别小心具体是由哪里释放要提前考虑好如果造成资源的多次释放可能会造成程序崩溃为时表示该所包含的内容是在一个用户创建的内存块中并且可以被在处理的过程中进行变更而不会造成问题为时表示该所包含的内容是在内存中但是这些内容却不能被进行处理的进行变更为时表示该所包含的内容是在内存中是通过使用内存映射从文件中映射到内存中的这些内容却不能被进行处理的进行变更可以回收的也就是这个是可以被释放的这个字段通常是配合字段一起使用的对于使用函数创建的并且是另外一个的那么可以使用这个字段来标示这个是可以被释放的为时表示该所包含的内容是在文件中遇到有字段被设置为的的则该的数据即便不是最后结束的数据被设置标志所有要输出的内容都完了也会进行输出不会受配置的限制但是会受到发送速率等其他条件的限制数据被以多个传递给了过滤器此字段为表明这是最后一个在当前的里面此是最后一个特别要注意的是的不一定是但是的一定是的这是因为数据会被以多个传递给某个模块在创建一个的的时候通常将新创建的一个的置为由于受到内存使用的限制有时候一些的内容需要被写到磁盘上的临时文件中去那么这时就设置此标志对于此对象的创建可以直接在某个上分配然后根据需要给对应的字段赋值也可以使用定义好的个宏这两个宏使用类似函数也是不说自明的对于创建字段为的就是其内容可以被后续的模块进行修改可以直接使用函数进行创建该函数创建一个类型的对象并返回指向这个对象的指针创建失败返回对于创建的这个对象它的和指向新分配内存开始和结束的地方和都指向这块新分配内存的开始处这样后续的操作可以在这块新分配的内存上存入数据分配该和使用的内存所使用的该使用的内存的大小为了配合对的使用定义了以下的宏方便操作返回这个里面的内容是否在内存里返回这个里面的内容是否仅仅在内存里并且没有在文件里返回该是否是一个特殊的只含有特殊的标志和没有包含真正的数据返回该是否是一个只包含标志而不包含真正数据的特殊返回该所含数据的大小不管这个数据是在文件里还是在内存里顾名思义看起来好像是一个的数据结构这样的说法算对也不算对因为它符合类型数据结构的一些特点比如可以添加元素实现自增长不会像数组类型的数据结构受到初始设定的数组容量的限制并且它跟我们常见的型数据结构也是一样的内部实现使用了一个链表那么它跟我们常见的链表实现的有什么不同呢不同点就在于它的节点它的节点不像我们常见的的节点只能存放一个元素的节点实际上是一个固定大小的数组在初始化的时候我们需要设定元素需要占用的空间大小每个节点数组的容量大小在添加元素到这个里面的时候会在最尾部的节点里的数组上添加元素如果这个节点的数组存满了就再增加一个新的节点到这个里面去好了看到这里大家应该基本上明白这个结构了吧还不明白也没有关系下面我们来具体看一下它的定义这些定义和相关的操作函数定义在文件中指向该链表的最后一个节点该链表的首个存放具体元素的节点链表中存放的具体元素所需内存大小每个节点所含的固定大小的数组的容量该使用的分配内存的好我们在看一下每个节点的定义节点中存放具体元素的内存的开始地址节点中已有元素个数这个值是不能大于链表头节点类型中的字段的指向下一个节点我们来看一下提供的一个操作的函数该函数创建一个类型的对象并对该的第一个节点分配存放元素的内存空间分配内存使用的每个节点固定长度的数组的长度即最多可以存放的元素个数每个元素所占用的内存大小返回值成功返回指向创建的对象的指针失败返回该函数在给定的的尾部追加一个元素并返回指向新元素存放空间的指针如果追加失败则返回该函数是用于类型的对象已经存在但是其第一个节点存放元素的内存空间还未分配的情况下可以调用此函数来给这个的首节点来分配存放元素的内存空间那么什么时候会出现已经有了类型的对象而其首节点存放元素的内存尚未分配的情况呢那就是这个类型的变量并不是通过调用函数创建的例如如果某个结构体的一个成员变量是类型的那么当这个结构体类型的对象被创建出来的时候这个成员变量也被创建出来了但是它的首节点的存放元素的内存并未被分配总之如果这个类型的变量如果不是你通过调用函数创建的那么就必须调用此函数去初始化否则你往这个里追加元素就可能引发不可预知的行为亦或程序会崩溃是中的双向链表在源码目录下面的里面它的原型如下不同于教科书中将链表节点的数据成员声明在链表节点的结构体中只是声明了前向和后向指针在使用的时候我们首先需要定义一个哨兵节点对于后续具体存放数据的节点我们称之为数据节点比如接下来需要进行初始化通过宏来实现的宏定义如下可见初始的时候哨兵节点的和都指向自己因此其实是一个空链表可以用来判断一个链表是否为空其实现也很简单就是那么如何声明一个具有数据元素的链表节点呢只要在相应的结构体中加上一个的成员就行了比如中的对于每一个这样的数据节点可以通过来添加到链表中第一个参数是哨兵节点第二个参数是数据节点比如相应的几个宏定义如下和都是往头部添加节点是往尾部添加节点从代码可以看出哨兵节点的指向链表的尾数据节点指向链表的头数据节点另外和这两个宏分别可以得到头节点和尾节点那假如现在有一个指向的是链表中的数据节点的成员如何得到的数据呢提供了宏来得到的指针例如也许您已经可以猜到是通过地址相减来得到的另外也提供了宏来从链表中删除一个数据节点以及用来将一个链表添加到另一个链表",isPost:!0,isHome:!1,isHighlightShrink:!0,isToc:!0,postUpdate:"2020-02-06 22:29:54",postMainColor:""}</script><noscript><style>#nav{opacity:1}.justified-gallery img{opacity:1}#post-meta time,#recent-posts time{display:inline!important}</style></noscript><script>(e=>{e.saveToLocal={set:(e,t,a)=>{var o;0!==a&&(o=Date.now(),localStorage.setItem(e,JSON.stringify({value:t,expiry:o+864e5*a})))},get:e=>{var t=localStorage.getItem(e);if(t){t=JSON.parse(t);if(!(Date.now()>t.expiry))return t.value;localStorage.removeItem(e)}}},e.getScript=(o,c={})=>new Promise((t,e)=>{const a=document.createElement("script");a.src=o,a.async=!0,a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},Object.keys(c).forEach(e=>{a.setAttribute(e,c[e])}),document.head.appendChild(a)}),e.getCSS=(o,c=!1)=>new Promise((t,e)=>{const a=document.createElement("link");a.rel="stylesheet",a.href=o,c&&(a.id=c),a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},document.head.appendChild(a)}),e.activateDarkMode=()=>{document.documentElement.setAttribute("data-theme","dark"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#0d0d0d")},e.activateLightMode=()=>{document.documentElement.setAttribute("data-theme","light"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#ffffff")};var e=saveToLocal.get("theme"),t=window.matchMedia("(prefers-color-scheme: dark)").matches,a=window.matchMedia("(prefers-color-scheme: light)").matches,o=window.matchMedia("(prefers-color-scheme: no-preference)").matches,c=!t&&!a&&!o,t=(void 0===e?(a?activateLightMode():t?activateDarkMode():(o||c)&&((a=(new Date).getHours())<=6||18<=a?activateDarkMode:activateLightMode)(),window.matchMedia("(prefers-color-scheme: dark)").addListener(e=>{void 0===saveToLocal.get("theme")&&(e.matches?activateDarkMode:activateLightMode)()})):("light"===e?activateLightMode:activateDarkMode)(),saveToLocal.get("aside-status"));void 0!==t&&("hide"===t?document.documentElement.classList.add("hide-aside"):document.documentElement.classList.remove("hide-aside"));/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)&&document.documentElement.classList.add("apple")})(window)</script><link rel="stylesheet" href="/css/1.min.css?1" media="async" onload='this.media="all"'><link rel="stylesheet" href="/css/bg.css?1" media="async" onload='this.media="all"'><meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="云少IT" type="application/atom+xml"><link rel="alternate" href="/rss.xml" title="云少IT" type="application/rss+xml"></head><body data-type="anzhiyu"><div id="web_bg"></div><div id="an_music_bg"></div><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.10/progress_bar/progress_bar.css"><script async src="https://cdn.cbd.int/pace-js@1.2.4/pace.min.js" data-pace-options="{ &quot;restartOnRequestAfter&quot;:false,&quot;eventLag&quot;:false}"></script><div class="post" id="body-wrap"><header class="post-bg" id="page-header"><nav id="nav"><div id="nav-group"><span id="blog_name"><div class="back-home-button"><i class="anzhiyufont anzhiyu-icon-grip-vertical"></i><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div></div><a id="site-name" href="/" accesskey="h"><div class="title">云少IT</div><i class="anzhiyufont anzhiyu-icon-house-chimney"></i></a></span><div class="mask-name-container"><div id="name-container"><a id="page-name" href="javascript:anzhiyu.scrollToDest(0, 500)" rel="external nofollow noreferrer">PAGE_NAME</a></div></div><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div></div><div id="nav-right"><div class="nav-button only-home" id="travellings_button" title="随机前往一个开往项目网站"><a class="site-page" onclick="anzhiyu.totraveling()" title="随机前往一个开往项目网站" href="javascript:void(0);" rel="external nofollow" data-pjax-state="external"><i class="anzhiyufont anzhiyu-icon-train"></i></a></div><div class="nav-button" id="randomPost_button"><a class="site-page" onclick="toRandomPost()" title="随机前往一个文章" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-dice"></i></a></div><div class="nav-button" id="search-button"><a class="site-page social-icon search" href="javascript:void(0);" rel="external nofollow noreferrer" title="搜索🔍" accesskey="s"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span> 搜索</span></a></div><input id="center-console" type="checkbox"><label class="widget" for="center-console" title="中控台" onclick="anzhiyu.switchConsole()"><i class="left"></i><i class="widget center"></i><i class="widget right"></i></label><div id="console"><div class="console-card-group-reward"><ul class="reward-all console-card"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" alt="wechat" src="/img/wxpay.webp"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" alt="alipay" src="/img/alipay.webp"></a><div class="post-qr-code-desc">alipay</div></li></ul></div><div class="console-card-group"><div class="console-card-group-left"><div class="console-card" id="card-newest-comments"><div class="card-content"><div class="author-content-item-tips">互动</div><span class="author-content-item-title">最新评论</span></div><div class="aside-list"><span>正在加载中...</span></div></div></div><div class="console-card-group-right"><div class="console-card tags"><div class="card-content"><div class="author-content-item-tips">兴趣点</div><span class="author-content-item-title">寻找你感兴趣的领域</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:1.05rem;color:#637571">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:1.05rem;color:#8a0460">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:1.05rem;color:#6b3641">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:1.05rem;color:#5e6603">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:1.05rem;color:#abb61f">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:1.05rem;color:#6dc55c">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:1.05rem;color:#8745c2">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:1.05rem;color:#804042">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:1.05rem;color:#21b4c6">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:1.05rem;color:#0a3988">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:1.05rem;color:#29446d">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:1.05rem;color:#afc378">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:1.05rem;color:#b10843">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:1.05rem;color:#9663a4">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:1.05rem;color:#b4214d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:1.05rem;color:#220f01">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:1.05rem;color:#c4106c">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:1.05rem;color:#1f5f9f">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:1.05rem;color:#c3563b">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:1.05rem;color:#1a3364">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:1.05rem;color:#1ea132">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:1.05rem;color:#a00679">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:1.05rem;color:#9e5e9b">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:1.05rem;color:#4d47bc">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:1.05rem;color:#a56245">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:1.05rem;color:#9c8435">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:1.05rem;color:#647e28">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:1.05rem;color:#a192b9">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:1.05rem;color:#22a370">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:1.05rem;color:#6b985c">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:1.05rem;color:#bc5f40">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:1.05rem;color:#167366">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:1.05rem;color:#aa928f">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:1.05rem;color:#3a740b">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:1.05rem;color:#75c343">面试<sup>11</sup></a></div></div><hr></div></div><div class="console-card history"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-box-archiv"></i><span>文章</span></div><div class="card-archives"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-archive"></i><span>归档</span><a class="card-more-btn" href="/archives/" title="查看更多"> <i class="anzhiyufont anzhiyu-icon-angle-right"></i></a></div><ul class="card-archive-list"><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/06/"><span class="card-archive-list-date">六月 2023</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2021/05/"><span class="card-archive-list-date">五月 2021</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/10/"><span class="card-archive-list-date">十月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">21</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/09/"><span class="card-archive-list-date">九月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">44</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/08/"><span class="card-archive-list-date">八月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">47</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/07/"><span class="card-archive-list-date">七月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">42</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/06/"><span class="card-archive-list-date">六月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">31</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/05/"><span class="card-archive-list-date">五月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">43</span><span>篇</span></div></a></li></ul></div><hr></div></div></div><div class="button-group"><div class="console-btn-item"><a class="darkmode_switchbutton" title="显示模式切换" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-moon"></i></a></div><div class="console-btn-item" id="consoleHideAside" onclick="anzhiyu.hideAsideBtn()" title="边栏显示控制"><a class="asideSwitch"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></a></div><div class="console-btn-item on" id="consoleCommentBarrage" onclick="anzhiyu.switchCommentBarrage()" title="热评开关"><a class="commentBarrage"><i class="anzhiyufont anzhiyu-icon-message"></i></a></div><div class="console-btn-item" id="consoleMusic" onclick="anzhiyu.musicToggle()" title="音乐开关"><a class="music-switch"><i class="anzhiyufont anzhiyu-icon-music"></i></a></div></div><div class="console-mask" onclick="anzhiyu.hideConsole()" href="javascript:void(0);" rel="external nofollow noreferrer"></div></div><div class="nav-button" id="nav-totop"><a class="totopbtn" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i><span id="percent" onclick="anzhiyu.scrollToDest(0,500)">0</span></a></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer" title="切换"><i class="anzhiyufont anzhiyu-icon-bars"></i></a></div></div></div></nav><div id="post-info"><div id="post-firstinfo"><div class="meta-firstline"><a class="post-meta-original">原创</a><span class="article-meta tags"></span></div></div><h1 class="post-title" itemprop="name headline">六、Nginx 基本数据结构</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="anzhiyufont anzhiyu-icon-calendar-days post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" itemprop="dateCreated datePublished" datetime="2020-02-06T14:29:54.000Z" title="发表于 2020-02-06 22:29:54">2020-02-06</time><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-history post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" itemprop="dateCreated datePublished" datetime="2020-02-06T14:29:54.000Z" title="更新于 2020-02-06 22:29:54">2020-02-06</time></span></div><div class="meta-secondline"><span class="post-meta-separator"></span><span class="post-meta-wordcount"><i class="anzhiyufont anzhiyu-icon-file-word post-meta-icon" title="文章字数"></i><span class="post-meta-label" title="文章字数">字数总计:</span><span class="word-count" title="文章字数">12.4k</span><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-clock post-meta-icon" title="阅读时长"></i><span class="post-meta-label" title="阅读时长">阅读时长:</span><span>42分钟</span></span><span class="post-meta-separator"></span><span class="post-meta-pv-cv" data-flag-title="六、Nginx 基本数据结构"><i class="anzhiyufont anzhiyu-icon-fw-eye post-meta-icon"></i><span class="post-meta-label" title="阅读量">阅读量:</span><span id="busuanzi_value_page_pv"><i class="anzhiyufont anzhiyu-icon-spinner anzhiyu-spin"></i></span></span><span class="post-meta-separator"> </span><span class="post-meta-position" title="作者IP属地为武汉"><i class="anzhiyufont anzhiyu-icon-location-dot"></i>武汉</span></div></div></div><section class="main-hero-waves-area waves-area"><svg class="waves-svg" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"><defs><path id="gentle-wave" d="M -160 44 c 30 0 58 -18 88 -18 s 58 18 88 18 s 58 -18 88 -18 s 58 18 88 18 v 44 h -352 Z"></path></defs><g class="parallax"><use href="#gentle-wave" x="48" y="0"></use><use href="#gentle-wave" x="48" y="3"></use><use href="#gentle-wave" x="48" y="5"></use><use href="#gentle-wave" x="48" y="7"></use></g></svg></section><div id="post-top-cover"><img class="nolazyload" id="post-top-bg" src="https://img.xjh.me/random_img.php?type=bg&amp;ctype=nature&amp;return=302&amp;_r_=b4b7cfea-fdef-8d0f-cb5d-61fdc2167c9f"></div></header><main id="blog-container"><div class="layout" id="content-inner"><div id="post"><div class="post-ai-description"><div class="ai-title"><i class="anzhiyufont anzhiyu-icon-bilibili"></i><div class="ai-title-text">AI-摘要</div><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i><i class="anzhiyufont anzhiyu-icon-circle-dot" title="朗读摘要"></i><div id="ai-tag">Tianli GPT</div></div><div class="ai-explanation">AI初始化中...</div><div class="ai-btn-box"><div class="ai-btn-item">介绍自己 🙈</div><div class="ai-btn-item">生成本文简介 👋</div><div class="ai-btn-item">推荐相关文章 📖</div><div class="ai-btn-item">前往主页 🏠</div><div class="ai-btn-item" id="go-tianli-blog">前往爱发电购买</div></div><script data-pjax src="/js/anzhiyu/ai_abstract.js"></script></div><article class="post-content" id="article-container" itemscope itemtype="https://it985.github.io/posts/c504cd62.html"><header><h1 id="CrawlerTitle" itemprop="name headline">六、Nginx 基本数据结构</h1><span itemprop="author" itemscope itemtype="http://schema.org/Person">云少</span><time itemprop="dateCreated datePublished" datetime="2020-02-06T14:29:54.000Z" title="发表于 2020-02-06 22:29:54">2020-02-06</time><time itemprop="dateCreated datePublished" datetime="2020-02-06T14:29:54.000Z" title="更新于 2020-02-06 22:29:54">2020-02-06</time></header><h1 id="六、Nginx-基本数据结构"><a href="#六、Nginx-基本数据结构" class="headerlink" title="六、Nginx 基本数据结构"></a>六、Nginx 基本数据结构</h1><p>Nginx 的作者为追求极致的高效，自己实现了很多颇具特色的 Nginx 风格的数据结构以及公共函数。比如，Nginx 提供了带长度的字符串，根据编译器选项优化过的字符串拷贝函数 ngx_copy 等。所以，在我们写 Nginx 模块时，应该尽量调用 Nginx 提供的 api，尽管有些 api 只是对 glibc 的宏定义。本节，我们介绍 string、list、buffer、chain 等一系列最基本的数据结构及相关api的使用技巧以及注意事项。</p><h2 id="ngx-str-t"><a href="#ngx-str-t" class="headerlink" title="ngx_str_t"></a>ngx_str_t</h2><p>在 Nginx 源码目录的 src&#x2F;core 下面的 <code>ngx_string.h|c</code> 里面，包含了字符串的封装以及字符串相关操作的 api。Nginx 提供了一个带长度的字符串结构 ngx_str_t，它的原型如下：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct &#123;</span><br><span class="line">    <span class="attribute">size_t</span>      len;</span><br><span class="line">    <span class="attribute">u_char</span>     *data;</span><br><span class="line">&#125; ngx_str_t;</span><br></pre></td></tr></table></figure><p>在结构体当中，data 指向字符串数据的第一个字符，字符串的结束用长度来表示，而不是由<code>&#39;\\0&#39;</code>来表示结束。所以，在写 Nginx 代码时，处理字符串的方法跟我们平时使用有很大的不一样，但要时刻记住，字符串不以<code>&#39;\\0&#39;</code>结束，尽量使用 Nginx 提供的字符串操作的 api 来操作字符串。</p><p>那么，Nginx 这样做有什么好处呢？首先，通过长度来表示字符串长度，减少计算字符串长度的次数。其次，Nginx 可以重复引用一段字符串内存，data 可以指向任意内存，长度表示结束，而不用去 copy 一份自己的字符串(因为如果要以<code>&#39;\\0&#39;</code>结束，而不能更改原字符串，所以势必要 copy 一段字符串)。我们在 ngx_http_request_t 结构体的成员中，可以找到很多字符串引用一段内存的例子，比如 request_line、uri、args 等等，这些字符串的 data 部分，都是指向在接收数据时创建 buffer 所指向的内存中，uri，args 就没有必要 copy 一份出来。这样的话，减少了很多不必要的内存分配与拷贝。</p><p>正是基于此特性，在 Nginx 中，必须谨慎的去修改一个字符串。在修改字符串时需要认真的去考虑：是否可以修改该字符串；字符串修改后，是否会对其它的引用造成影响。在后面介绍 ngx_unescape_uri 函数的时候，就会看到这一点。但是，使用 Nginx 的字符串会产生一些问题，glibc 提供的很多系统 api 函数大多是通过<code>&#39;\\0&#39;</code>来表示字符串的结束，所以我们在调用系统 api 时，就不能直接传入 str-&gt;data 了。此时，通常的做法是创建一段 str-&gt;len + 1 大小的内存，然后 copy 字符串，最后一个字节置为<code>&#39;\\0&#39;</code>。比较 hack 的做法是，将字符串最后一个字符的后一个字符 backup 一个，然后设置为<code>&#39;\\0&#39;</code>，在做完调用后，再由 backup 改回来，但前提条件是，你得确定这个字符是可以修改的，而且是有内存分配，不会越界，但一般不建议这么做。接下来，看看 Nginx 提供的操作字符串相关的 api。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_string(str)     &#123; sizeof(str) - 1, (u_char *) str &#125;</span></span><br></pre></td></tr></table></figure><p>ngx_string(str) 是一个宏，它通过一个以<code>&#39;\\0&#39;</code>结尾的普通字符串 str 构造一个 Nginx 的字符串，鉴于其中采用 sizeof 操作符计算字符串长度，因此参数必须是一个常量字符串。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_null_string     &#123; 0, NULL &#125;</span></span><br></pre></td></tr></table></figure><p>定义变量时，使用 ngx_null_string 初始化字符串为空字符串，符串的长度为 0，data 为 NULL。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_str_set(str, text)                                               \</span></span><br><span class="line">        (str)-&gt;<span class="attribute">len</span> = sizeof(text) - <span class="number">1</span>; (str)-&gt;<span class="attribute">data</span> = (u_char *) text</span><br></pre></td></tr></table></figure><p>ngx_str_set 用于设置字符串 str 为 text，由于使用 sizeof 计算长度，故 text 必须为常量字符串。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_str_null(str)   (str)-&gt;len = 0; (str)-&gt;data = NULL</span></span><br></pre></td></tr></table></figure><p>ngx_str_null 用于设置字符串 str 为空串，长度为 0，data 为 NULL。</p><p>上面这四个函数，使用时一定要小心，ngx_string 与 ngx_null_string 是“{<em>，</em>}”格式的，故只能用于赋值时初始化，如：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_str_t</span> str = ngx_string(<span class="string">&quot;hello world&quot;</span>);</span><br><span class="line"><span class="attribute">ngx_str_t</span> str1 = ngx_null_string;</span><br></pre></td></tr></table></figure><p>如果向下面这样使用，就会有问题，这里涉及到c语言中对结构体变量赋值操作的语法规则，在此不做介绍。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_str_t</span> str, str1;</span><br><span class="line"><span class="attribute">str</span> = ngx_string(<span class="string">&quot;hello world&quot;</span>);    // 编译出错</span><br><span class="line"><span class="attribute">str1</span> = ngx_null_string;                // 编译出错</span><br></pre></td></tr></table></figure><p>这种情况，可以调用 ngx_str_set 与 ngx_str_null 这两个函数来做:</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_str_t</span> str, str1;</span><br><span class="line">ngx_str_set(&amp;str, &quot;<span class="attribute">hello</span> world<span class="string">&quot;);    </span></span><br><span class="line"><span class="string">ngx_str_null(&amp;str1);</span></span><br></pre></td></tr></table></figure><p>按照 C99 标准，您也可以这么做：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_str_t</span> str, str1;</span><br><span class="line"><span class="attribute">str</span>  = (ngx_str_t) ngx_string(<span class="string">&quot;hello world&quot;</span>);</span><br><span class="line"><span class="attribute">str1</span> = (ngx_str_t) ngx_null_string;</span><br></pre></td></tr></table></figure><p>另外要注意的是，ngx_string 与 ngx_str_set 在调用时，传进去的字符串一定是常量字符串，否则会得到意想不到的错误(因为 ngx_str_set 内部使用了 sizeof()，如果传入的是 <code>u_char*</code>，那么计算的是这个指针的长度，而不是字符串的长度)。如：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_str_t</span> str;</span><br><span class="line"><span class="attribute">u_char</span> *a = <span class="string">&quot;hello world&quot;</span>;</span><br><span class="line">ngx_str_set(&amp;str, a);    // 问题产生</span><br></pre></td></tr></table></figure><p>此外，值得注意的是，由于 ngx_str_set 与 ngx_str_null 实际上是两行语句，故在 if&#x2F;for&#x2F;while 等语句中单独使用需要用花括号括起来，例如：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_str_t</span> str;</span><br><span class="line"><span class="attribute">if</span> (cond)</span><br><span class="line">   ngx_str_set(&amp;str, <span class="string">&quot;true&quot;</span>);     // 问题产生</span><br><span class="line"><span class="attribute">else</span></span><br><span class="line">   ngx_str_set(&amp;str, <span class="string">&quot;false&quot;</span>);    // 问题产生</span><br><span class="line"><span class="attribute">void</span> ngx_strlow(u_char *dst, u_char *src, size_t n);</span><br></pre></td></tr></table></figure><p>将 src 的前 n 个字符转换成小写存放在 dst 字符串当中，调用者需要保证 dst 指向的空间大于等于n，且指向的空间必须可写。操作不会对原字符串产生变动。如要更改原字符串，可以：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ngx_strlow(str-&gt;data, str-&gt;data, str-&gt;len);</span><br><span class="line">ngx_strncmp(s1, s2, n)</span><br></pre></td></tr></table></figure><p>区分大小写的字符串比较，只比较前n个字符。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ngx_strcmp(s1, s2)</span><br></pre></td></tr></table></figure><p>区分大小写的不带长度的字符串比较。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_int_t</span> ngx_strcasecmp(u_char *s1, u_char *s2);</span><br></pre></td></tr></table></figure><p>不区分大小写的不带长度的字符串比较。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_int_t</span> ngx_strncasecmp(u_char *s1, u_char *s2, size_t n);</span><br></pre></td></tr></table></figure><p>不区分大小写的带长度的字符串比较，只比较前 n 个字符。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">u_char</span> * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);</span><br><span class="line"><span class="attribute">u_char</span> * ngx_cdecl ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...);</span><br><span class="line"><span class="attribute">u_char</span> * ngx_cdecl ngx_slprintf(u_char *buf, u_char *<span class="literal">last</span>, const char *fmt, ...);</span><br></pre></td></tr></table></figure><p>上面这三个函数用于字符串格式化，ngx_snprintf 的第二个参数 max 指明 buf 的空间大小，ngx_slprintf 则通过 last 来指明 buf 空间的大小。推荐使用第二个或第三个函数来格式化字符串，ngx_sprintf 函数还是比较危险的，容易产生缓冲区溢出漏洞。在这一系列函数中，Nginx 在兼容 glibc 中格式化字符串的形式之外，还添加了一些方便格式化 Nginx 类型的一些转义字符，比如<code>%V</code>用于格式化 ngx_str_t 结构。在 Nginx 源文件的 ngx_string.c 中有说明：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line"> * <span class="attribute">supported</span> formats:</span><br><span class="line"> *    %[<span class="number">0</span>][width][x][X]O        off_t</span><br><span class="line"> *    %[<span class="number">0</span>][width]T              time_t</span><br><span class="line"> *    %[<span class="number">0</span>][width][u][x|X]z      ssize_t/size_t</span><br><span class="line"> *    %[<span class="number">0</span>][width][u][x|X]d      int/u_int</span><br><span class="line"> *    %[<span class="number">0</span>][width][u][x|X]l      long</span><br><span class="line"> *    %[<span class="number">0</span>][width|m][u][x|X]i    ngx_int_t/ngx_uint_t</span><br><span class="line"> *    %[<span class="number">0</span>][width][u][x|X]D      int32_t/uint32_t</span><br><span class="line"> *    %[<span class="number">0</span>][width][u][x|X]L      int64_t/uint64_t</span><br><span class="line"> *    %[<span class="number">0</span>][width|m][u][x|X]A    ngx_atomic_int_t/ngx_atomic_uint_t</span><br><span class="line"> *    %[<span class="number">0</span>][width][.width]f      double, max valid number fits to %<span class="number">18</span>.15f</span><br><span class="line"> *    %P                        ngx_pid_t</span><br><span class="line"> *    %M                        ngx_msec_t</span><br><span class="line"> *    %r                        rlim_t</span><br><span class="line"> *    %p                        void *</span><br><span class="line"> *    %V                        ngx_str_t *</span><br><span class="line"> *    %v                        ngx_variable_value_t *</span><br><span class="line"> *    %s                        null-terminated string</span><br><span class="line"> *    %*s                       length and string</span><br><span class="line"> *    %Z                        <span class="string">&#x27;\0&#x27;</span></span><br><span class="line"> *    %N                        <span class="string">&#x27;\n&#x27;</span></span><br><span class="line"> *    %c                        char</span><br><span class="line"> *    %%                        %</span><br><span class="line"> *</span><br><span class="line"> *  reserved:</span><br><span class="line"> *    %t                        ptrdiff_t</span><br><span class="line"> *    %S                        null-terminated wchar string</span><br><span class="line"> *    %C                        wchar</span><br><span class="line"> */</span><br></pre></td></tr></table></figure><p>这里特别要提醒的是，我们最常用于格式化 ngx_str_t 结构，其对应的转义符是<code>%V</code>，传给函数的一定要是指针类型，否则程序就会 coredump 掉。这也是我们最容易犯的错。比如：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_str_t</span> str = ngx_string(<span class="string">&quot;hello world&quot;</span>);</span><br><span class="line"><span class="attribute">u_char</span> buffer[<span class="number">1024</span>];</span><br><span class="line">ngx_snprintf(buffer, 1024, &quot;%V&quot;, &amp;str);    // 注意，str取地址</span><br><span class="line"><span class="attribute">void</span> ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src);</span><br><span class="line"> <span class="attribute">ngx_int_t</span> ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);</span><br></pre></td></tr></table></figure><p>这两个函数用于对 str 进行 base64 编码与解码，调用前，需要保证 dst 中有足够的空间来存放结果，如果不知道具体大小，可先调用 ngx_base64_encoded_length 与 ngx_base64_decoded_length 来预估最大占用空间。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">uintptr_t</span> ngx_escape_uri(u_char *dst, u_char *src, size_t size,</span><br><span class="line">        ngx_uint_t type);</span><br></pre></td></tr></table></figure><p>对 src 进行编码，根据 type 来按不同的方式进行编码，如果 dst 为 NULL，则返回需要转义的字符的数量，由此可得到需要的空间大小。type 的类型可以是：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define NGX_ESCAPE_URI         0</span></span><br><span class="line"><span class="comment">#define NGX_ESCAPE_ARGS        1</span></span><br><span class="line"><span class="comment">#define NGX_ESCAPE_HTML        2</span></span><br><span class="line"><span class="comment">#define NGX_ESCAPE_REFRESH     3</span></span><br><span class="line"><span class="comment">#define NGX_ESCAPE_MEMCACHED   4</span></span><br><span class="line"><span class="comment">#define NGX_ESCAPE_MAIL_AUTH   5</span></span><br><span class="line"><span class="attribute">void</span> ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type);</span><br></pre></td></tr></table></figure><p>对 src 进行反编码，type 可以是 0、NGX_UNESCAPE_URI、NGX_UNESCAPE_REDIRECT 这三个值。如果是 0，则表示 src 中的所有字符都要进行转码。如果是 NGX_UNESCAPE_URI 与 NGX_UNESCAPE_REDIRECT，则遇到<code>&#39;?&#39;</code>后就结束了，后面的字符就不管了。而 NGX_UNESCAPE_URI 与 NGX_UNESCAPE_REDIRECT 之间的区别是 NGX_UNESCAPE_URI 对于遇到的需要转码的字符，都会转码，而 NGX_UNESCAPE_REDIRECT 则只会对非可见字符进行转码。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">uintptr_t</span> ngx_escape_html(u_char *dst, u_char *src, size_t size);</span><br></pre></td></tr></table></figure><p>对 html 标签进行编码。</p><p>当然，我这里只介绍了一些常用的 api 的使用，大家可以先熟悉一下，在实际使用过程中，遇到不明白的，最快最直接的方法就是去看源码，看 api 的实现或看 Nginx 自身调用 api 的地方是怎么做的，代码就是最好的文档。</p><h2 id="ngx-pool-t"><a href="#ngx-pool-t" class="headerlink" title="ngx_pool_t"></a>ngx_pool_t</h2><p>ngx_pool_t是一个非常重要的数据结构，在很多重要的场合都有使用，很多重要的数据结构也都在使用它。那么它究竟是一个什么东西呢？简单的说，它提供了一种机制，帮助管理一系列的资源（如内存，文件等），使得对这些资源的使用和释放统一进行，免除了使用过程中考虑到对各种各样资源的什么时候释放，是否遗漏了释放的担心。</p><p>例如对于内存的管理，如果我们需要使用内存，那么总是从一个 ngx_pool_t 的对象中获取内存，在最终的某个时刻，我们销毁这个 ngx_pool_t 对象，所有这些内存都被释放了。这样我们就不必要对对这些内存进行 malloc 和 free 的操作，不用担心是否某块被malloc出来的内存没有被释放。因为当 ngx_pool_t 对象被销毁的时候，所有从这个对象中分配出来的内存都会被统一释放掉。</p><p>再比如我们要使用一系列的文件，但是我们打开以后，最终需要都关闭，那么我们就把这些文件统一登记到一个 ngx_pool_t 对象中，当这个 ngx_pool_t 对象被销毁的时候，所有这些文件都将会被关闭。</p><p>从上面举的两个例子中我们可以看出，使用 ngx_pool_t 这个数据结构的时候，所有的资源的释放都在这个对象被销毁的时刻，统一进行了释放，那么就会带来一个问题，就是这些资源的生存周期（或者说被占用的时间）是跟 ngx_pool_t 的生存周期基本一致（ngx_pool_t 也提供了少量操作可以提前释放资源）。从最高效的角度来说，这并不是最好的。比如，我们需要依次使用 A，B，C 三个资源，且使用完 B 的时候，A 就不会再被使用了，使用C的时候 A 和 B 都不会被使用到。如果不使用 ngx_pool_t 来管理这三个资源，那我们可能从系统里面申请 A，使用 A，然后在释放 A。接着申请 B，使用 B，再释放 B。最后申请 C，使用 C，然后释放 C。但是当我们使用一个 ngx_pool_t 对象来管理这三个资源的时候，A，B 和 C 的释放是在最后一起发生的，也就是在使用完 C 以后。诚然，这在客观上增加了程序在一段时间的资源使用量。但是这也减轻了程序员分别管理三个资源的生命周期的工作。这也就是有所得，必有所失的道理。实际上是一个取舍的问题，要看在具体的情况下，你更在乎的是哪个。</p><p>可以看一下在 Nginx 里面一个典型的使用 ngx_pool_t 的场景，对于 Nginx 处理的每个 http request, Nginx 会生成一个 ngx_pool_t 对象与这个 http request 关联，所有处理过程中需要申请的资源都从这个 ngx_pool_t 对象中获取，当这个 http request 处理完成以后，所有在处理过程中申请的资源，都将随着这个关联的 ngx_pool_t 对象的销毁而释放。</p><p>ngx_pool_t 相关结构及操作被定义在文件<code>src/core/ngx_palloc.h|c</code>中。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct ngx_pool_s        ngx_pool_t; </span><br><span class="line"></span><br><span class="line"><span class="attribute">struct</span> ngx_pool_s &#123;</span><br><span class="line">    <span class="attribute">ngx_pool_data_t</span>       d;</span><br><span class="line">    <span class="attribute">size_t</span>                max;</span><br><span class="line">    <span class="attribute">ngx_pool_t</span>           *current;</span><br><span class="line">    <span class="attribute">ngx_chain_t</span>          *chain;</span><br><span class="line">    <span class="attribute">ngx_pool_large_t</span>     *large;</span><br><span class="line">    <span class="attribute">ngx_pool_cleanup_t</span>   *cleanup;</span><br><span class="line">    <span class="attribute">ngx_log_t</span>            *log;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>从 ngx_pool_t 的一般使用者的角度来说，可不用关注 ngx_pool_t 结构中各字段作用。所以这里也不会进行详细的解释，当然在说明某些操作函数的使用的时候，如有必要，会进行说明。</p><p>下面我们来分别解释下 ngx_pool_t 的相关操作。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_pool_t</span> *ngx_create_pool(size_t size, ngx_log_t *log);</span><br></pre></td></tr></table></figure><p>创建一个初始节点大小为 size 的 pool，log 为后续在该 pool 上进行操作时输出日志的对象。 需要说明的是 size 的选择，size 的大小必须小于等于 NGX_MAX_ALLOC_FROM_POOL，且必须大于 sizeof(ngx_pool_t)。</p><p>选择大于 NGX_MAX_ALLOC_FROM_POOL 的值会造成浪费，因为大于该限制的空间不会被用到（只是说在第一个由 ngx_pool_t 对象管理的内存块上的内存，后续的分配如果第一个内存块上的空闲部分已用完，会再分配的）。</p><p>选择小于 sizeof(ngx_pool_t)的值会造成程序崩溃。由于初始大小的内存块中要用一部分来存储 ngx_pool_t 这个信息本身。</p><p>当一个 ngx_pool_t 对象被创建以后，该对象的 max 字段被赋值为 size-sizeof(ngx_pool_t)和 NGX_MAX_ALLOC_FROM_POOL 这两者中比较小的。后续的从这个 pool 中分配的内存块，在第一块内存使用完成以后，如果要继续分配的话，就需要继续从操作系统申请内存。当内存的大小小于等于 max 字段的时候，则分配新的内存块，链接在 d 这个字段（实际上是 d.next 字段）管理的一条链表上。当要分配的内存块是比 max 大的，那么从系统中申请的内存是被挂接在 large 字段管理的一条链表上。我们暂且把这个称之为大块内存链和小块内存链。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_palloc(ngx_pool_t *pool, size_t size); </span><br></pre></td></tr></table></figure><p>从这个 pool 中分配一块为 size 大小的内存。注意，此函数分配的内存的起始地址按照 NGX_ALIGNMENT 进行了对齐。对齐操作会提高系统处理的速度，但会造成少量内存的浪费。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_pnalloc(ngx_pool_t *pool, size_t size); </span><br></pre></td></tr></table></figure><p>从这个 pool 中分配一块为 size 大小的内存。但是此函数分配的内存并没有像上面的函数那样进行过对齐。</p><p>.. code:: c</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_pcalloc(ngx_pool_t *pool, size_t size);</span><br></pre></td></tr></table></figure><p>该函数也是分配size大小的内存，并且对分配的内存块进行了清零。内部实际上是转调用ngx_palloc实现的。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);</span><br></pre></td></tr></table></figure><p>按照指定对齐大小 alignment 来申请一块大小为 size 的内存。此处获取的内存不管大小都将被置于大内存块链中管理。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_int_t</span> ngx_pfree(ngx_pool_t *pool, void *p);</span><br></pre></td></tr></table></figure><p>对于被置于大块内存链，也就是被 large 字段管理的一列内存中的某块进行释放。该函数的实现是顺序遍历 large 管理的大块内存链表。所以效率比较低下。如果在这个链表中找到了这块内存，则释放，并返回 NGX_OK。否则返回 NGX_DECLINED。</p><p>由于这个操作效率比较低下，除非必要，也就是说这块内存非常大，确应及时释放，否则一般不需要调用。反正内存在这个 pool 被销毁的时候，总归会都释放掉的嘛！</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_pool_cleanup_t</span> *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size); </span><br></pre></td></tr></table></figure><p>ngx_pool_t 中的 cleanup 字段管理着一个特殊的链表，该链表的每一项都记录着一个特殊的需要释放的资源。对于这个链表中每个节点所包含的资源如何去释放，是自说明的。这也就提供了非常大的灵活性。意味着，ngx_pool_t 不仅仅可以管理内存，通过这个机制，也可以管理任何需要释放的资源，例如，关闭文件，或者删除文件等等。下面我们看一下这个链表每个节点的类型:</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;</span><br><span class="line"><span class="attribute">typedef</span> void (*ngx_pool_cleanup_pt)(void *data);</span><br><span class="line"></span><br><span class="line"><span class="attribute">struct</span> ngx_pool_cleanup_s &#123;</span><br><span class="line">    <span class="attribute">ngx_pool_cleanup_pt</span>   handler;</span><br><span class="line">    <span class="attribute">void</span>                 *data;</span><br><span class="line">    <span class="attribute">ngx_pool_cleanup_t</span>   *next;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>data: 指明了该节点所对应的资源。</li><li>handler: 是一个函数指针，指向一个可以释放 data 所对应资源的函数。该函数只有一个参数，就是 data。</li><li>next: 指向该链表中下一个元素。</li></ul><p>看到这里，ngx_pool_cleanup_add 这个函数的用法，我相信大家都应该有一些明白了。但是这个参数 size 是起什么作用的呢？这个 size 就是要存储这个 data 字段所指向的资源的大小，该函数会为 data 分配 size 大小的空间。</p><p>比如我们需要最后删除一个文件。那我们在调用这个函数的时候，把 size 指定为存储文件名的字符串的大小，然后调用这个函数给 cleanup 链表中增加一项。该函数会返回新添加的这个节点。我们然后把这个节点中的 data 字段拷贝为文件名。把 hander 字段赋值为一个删除文件的函数（当然该函数的原型要按照 <code>void (\*ngx_pool_cleanup_pt)(void \*data)</code>）。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> ngx_destroy_pool(ngx_pool_t *pool);</span><br></pre></td></tr></table></figure><p>该函数就是释放 pool 中持有的所有内存，以及依次调用 cleanup 字段所管理的链表中每个元素的 handler 字段所指向的函数，来释放掉所有该 pool 管理的资源。并且把 pool 指向的 ngx_pool_t 也释放掉了，完全不可用了。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> ngx_reset_pool(ngx_pool_t *pool);</span><br></pre></td></tr></table></figure><p>该函数释放 pool 中所有大块内存链表上的内存，小块内存链上的内存块都修改为可用。但是不会去处理 cleanup链表上的项目。</p><h2 id="ngx-array-t"><a href="#ngx-array-t" class="headerlink" title="ngx_array_t"></a>ngx_array_t</h2><p>ngx_array_t 是 Nginx 内部使用的数组结构。Nginx 的数组结构在存储上与大家认知的 C 语言内置的数组有相似性，比如实际上存储数据的区域也是一大块连续的内存。但是数组除了存储数据的内存以外还包含一些元信息来描述相关的一些信息。下面我们从数组的定义上来详细的了解一下。ngx_array_t 的定义位于<code>src/core/ngx_array.c|h</code>里面。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct ngx_array_s       ngx_array_t;</span><br><span class="line"><span class="attribute">struct</span> ngx_array_s &#123;</span><br><span class="line">    <span class="attribute">void</span>        *elts;</span><br><span class="line">    <span class="attribute">ngx_uint_t</span>   nelts;</span><br><span class="line">    <span class="attribute">size_t</span>       size;</span><br><span class="line">    <span class="attribute">ngx_uint_t</span>   nalloc;</span><br><span class="line">    <span class="attribute">ngx_pool_t</span>  *pool;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>elts: 指向实际的数据存储区域。</li><li>nelts: 数组实际元素个数。</li><li>size: 数组单个元素的大小，单位是字节。</li><li>nalloc: 数组的容量。表示该数组在不引发扩容的前提下，可以最多存储的元素的个数。当 nelts 增长到达 nalloc 时，如果再往此数组中存储元素，则会引发数组的扩容。数组的容量将会扩展到原有容量的 2 倍大小。实际上是分配新的一块内存，新的一块内存的大小是原有内存大小的 2 倍。原有的数据会被拷贝到新的一块内存中。</li><li>pool: 该数组用来分配内存的内存池。</li></ul><p>下面介绍 ngx_array_t 相关操作函数。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_array_t</span> *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);</span><br></pre></td></tr></table></figure><p>创建一个新的数组对象，并返回这个对象。</p><ul><li>p: 数组分配内存使用的内存池；</li><li>n: 数组的初始容量大小，即在不扩容的情况下最多可以容纳的元素个数。</li><li>size: 单个元素的大小，单位是字节。</li></ul><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> ngx_array_destroy(ngx_array_t *a);</span><br></pre></td></tr></table></figure><p>销毁该数组对象，并释放其分配的内存回内存池。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_array_push(ngx_array_t *a);</span><br></pre></td></tr></table></figure><p>在数组 a 上新追加一个元素，并返回指向新元素的指针。需要把返回的指针使用类型转换，转换为具体的类型，然后再给新元素本身或者是各字段（如果数组的元素是复杂类型）赋值。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);</span><br></pre></td></tr></table></figure><p>在数组 a 上追加 n 个元素，并返回指向这些追加元素的首个元素的位置的指针。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">static</span> ngx_inline ngx_int_t ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size);</span><br></pre></td></tr></table></figure><p>如果一个数组对象是被分配在堆上的，那么当调用 ngx_array_destroy 销毁以后，如果想再次使用，就可以调用此函数。</p><p>如果一个数组对象是被分配在栈上的，那么就需要调用此函数，进行初始化的工作以后，才可以使用。</p><p><strong>注意事项</strong>由于使用 ngx_palloc 分配内存，数组在扩容时，旧的内存不会被释放，会造成内存的浪费。因此，最好能提前规划好数组的容量，在创建或者初始化的时候一次搞定，避免多次扩容，造成内存浪费。</p><h2 id="ngx-hash-t"><a href="#ngx-hash-t" class="headerlink" title="ngx_hash_t"></a>ngx_hash_t</h2><p>ngx_hash_t 是 Nginx 自己的 hash 表的实现。定义和实现位于<code>src/core/ngx_hash.h|c</code>中。ngx_hash_t 的实现也与数据结构教科书上所描述的 hash 表的实现是大同小异。对于常用的解决冲突的方法有线性探测，二次探测和开链法等。ngx_hash_t 使用的是最常用的一种，也就是开链法，这也是 STL 中的 hash 表使用的方法。</p><p>但是 ngx_hash_t 的实现又有其几个显著的特点:</p><p>1、 ngx_hash_t 不像其他的 hash 表的实现，可以插入删除元素，它只能一次初始化，就构建起整个 hash 表以后，既不能再删除，也不能在插入元素了。<br>2、 ngx_hash_t 的开链并不是真的开了一个链表，实际上是开了一段连续的存储空间，几乎可以看做是一个数组。这是因为 ngx_hash_t 在初始化的时候，会经历一次预计算的过程，提前把每个桶里面会有多少元素放进去给计算出来，这样就提前知道每个桶的大小了。那么就不需要使用链表，一段连续的存储空间就足够了。这也从一定程度上节省了内存的使用。</p><p>从上面的描述，我们可以看出来，这个值越大，越造成内存的浪费。就两步，首先是初始化，然后就可以在里面进行查找了。下面我们详细来看一下。</p><p>ngx_hash_t 的初始化。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_int_t</span> ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,</span><br><span class="line">ngx_uint_t nelts);</span><br></pre></td></tr></table></figure><p>首先我们来看一下初始化函数。该函数的第一个参数 hinit 是初始化的一些参数的一个集合。 names 是初始化一个 ngx_hash_t 所需要的所有 key 的一个数组。而 nelts 就是 key 的个数。下面先看一下 ngx_hash_init_t 类型，该类型提供了初始化一个 hash 表所需要的一些基本信息。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct &#123;</span><br><span class="line">    <span class="attribute">ngx_hash_t</span>       *hash;</span><br><span class="line">    <span class="attribute">ngx_hash_key_pt</span>   key;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">ngx_uint_t</span>        max_size;</span><br><span class="line">    <span class="attribute">ngx_uint_t</span>        bucket_size;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">char</span>             *name;</span><br><span class="line">    <span class="attribute">ngx_pool_t</span>       *pool;</span><br><span class="line">    <span class="attribute">ngx_pool_t</span>       *temp_pool;</span><br><span class="line">&#125; ngx_hash_init_t;</span><br></pre></td></tr></table></figure><ul><li>hash: 该字段如果为 NULL，那么调用完初始化函数后，该字段指向新创建出来的 hash 表。如果该字段不为 NULL，那么在初始的时候，所有的数据被插入了这个字段所指的 hash 表中。</li><li>key: 指向从字符串生成 hash 值的 hash 函数。Nginx 的源代码中提供了默认的实现函数 ngx_hash_key_lc。</li><li>max_size: hash 表中的桶的个数。该字段越大，元素存储时冲突的可能性越小，每个桶中存储的元素会更少，则查询起来的速度更快。当然，这个值越大，越造成内存的浪费也越大，(实际上也浪费不了多少)。</li></ul><p>:bucket_size: 每个桶的最大限制大小，单位是字节。如果在初始化一个 hash 表的时候，发现某个桶里面无法存的下所有属于该桶的元素，则 hash 表初始化失败。</p><p>name: 该 hash 表的名字。</p><p>pool: 该 hash 表分配内存使用的 pool。</p><p>temp_pool: 该 hash 表使用的临时 pool，在初始化完成以后，该 pool 可以被释放和销毁掉。</p><p>下面来看一下存储 hash 表 key 的数组的结构。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct &#123;</span><br><span class="line">    <span class="attribute">ngx_str_t</span>         key;</span><br><span class="line">    <span class="attribute">ngx_uint_t</span>        key_hash;</span><br><span class="line">    <span class="attribute">void</span>             *value;</span><br><span class="line">&#125; ngx_hash_key_t;</span><br></pre></td></tr></table></figure><p>key 和 value 的含义显而易见，就不用解释了。key_hash 是对 key 使用 hash 函数计算出来的值。</p><p>对这两个结构分析完成以后，我想大家应该都已经明白这个函数应该是如何使用了吧。该函数成功初始化一个 hash 表以后，返回 NGX_OK，否则返回 NGX_ERROR。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);</span><br></pre></td></tr></table></figure><p>在 hash 里面查找 key 对应的 value。实际上这里的 key 是对真正的 key（也就是 name）计算出的 hash 值。len 是 name 的长度。</p><p>如果查找成功，则返回指向 value 的指针，否则返回 NULL。</p><h2 id="ngx-hash-wildcard-t"><a href="#ngx-hash-wildcard-t" class="headerlink" title="ngx_hash_wildcard_t"></a>ngx_hash_wildcard_t</h2><p>Nginx 为了处理带有通配符的域名的匹配问题，实现了 ngx_hash_wildcard_t 这样的 hash 表。他可以支持两种类型的带有通配符的域名。一种是通配符在前的，例如：<code>\*.abc.com</code>，也可以省略掉星号，直接写成<code>.abc.com</code>。这样的 key，可以匹配 <a target="_blank" rel="noopener external nofollow noreferrer" href="http://www.abc.com,qqq.www.abc.com/">www.abc.com，qqq.www.abc.com</a> 之类的。另外一种是通配符在末尾的，例如：<code>mail.xxx.\*</code>，请特别注意通配符在末尾的不像位于开始的通配符可以被省略掉。这样的通配符，可以匹配 mail.xxx.com、mail.xxx.com.cn、mail.xxx.net 之类的域名。</p><p>有一点必须说明，就是一个 ngx_hash_wildcard_t 类型的 hash 表只能包含通配符在前的key或者是通配符在后的key。不能同时包含两种类型的通配符的 key。ngx_hash_wildcard_t 类型变量的构建是通过函数 ngx_hash_wildcard_init 完成的，而查询是通过函数 ngx_hash_find_wc_head 或者 ngx_hash_find_wc_tail 来做的。ngx_hash_find_wc_head 查询包含通配符在前的 key 的 hash 表的，而 ngx_hash_find_wc_tail 是查询包含通配符在后的 key 的 hash 表的。</p><p>下面详细说明这几个函数的用法。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_int_t</span> ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,</span><br><span class="line">        ngx_uint_t nelts);</span><br></pre></td></tr></table></figure><p>该函数用来构建一个可以包含通配符 key 的 hash 表。</p><ul><li>hinit: 构造一个通配符 hash 表的一些参数的一个集合。关于该参数对应的类型的说明，请参见 ngx_hash_t 类型中 ngx_hash_init 函数的说明。</li><li>names: 构造此 hash 表的所有的通配符 key 的数组。特别要注意的是这里的 key 已经都是被预处理过的。例如：<code>\*.abc.com</code>或者<code>.abc.com</code>被预处理完成以后，变成了<code>com.abc.</code>。而<code>mail.xxx.\*</code>则被预处理为<code>mail.xxx.</code>。为什么会被处理这样？这里不得不简单地描述一下通配符 hash 表的实现原理。当构造此类型的 hash 表的时候，实际上是构造了一个 hash 表的一个“链表”，是通过 hash 表中的 key “链接”起来的。比如：对于<code>\*.abc.com</code>将会构造出 2 个 hash 表，第一个 hash 表中有一个 key 为 com 的表项，该表项的 value 包含有指向第二个 hash 表的指针，而第二个 hash 表中有一个表项 abc，该表项的 value 包含有指向<code>\*.abc.com</code>对应的 value 的指针。那么查询的时候，比如查询 <a target="_blank" rel="noopener external nofollow noreferrer" href="http://www.abc.com/">www.abc.com</a> 的时候，先查 com，通过查 com 可以找到第二级的 hash 表，在第二级 hash 表中，再查找 abc，依次类推，直到在某一级的 hash 表中查到的表项对应的 value 对应一个真正的值而非一个指向下一级 hash 表的指针的时候，查询过程结束。<strong>这里有一点需要特别注意的，就是 names 数组中元素的 value 值低两位 bit 必须为 0（有特殊用途）。如果不满足这个条件，这个 hash 表查询不出正确结果。</strong></li><li>nelts: names 数组元素的个数。</li></ul><p>该函数执行成功返回 NGX_OK，否则 NGX_ERROR。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);</span><br></pre></td></tr></table></figure><p>该函数查询包含通配符在前的 key 的 hash 表的。</p><ul><li>hwc: hash 表对象的指针。</li><li>name: 需要查询的域名，例如: <a target="_blank" rel="noopener external nofollow noreferrer" href="http://www.abc.com./">www.abc.com。</a></li><li>len: name 的长度。</li></ul><p>该函数返回匹配的通配符对应 value。如果没有查到，返回 NULL。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);</span><br></pre></td></tr></table></figure><p>该函数查询包含通配符在末尾的 key 的 hash 表的。</p><p>参数及返回值请参加上个函数的说明。</p><h2 id="ngx-hash-combined-t"><a href="#ngx-hash-combined-t" class="headerlink" title="ngx_hash_combined_t"></a>ngx_hash_combined_t</h2><p>组合类型 hash 表，该 hash 表的定义如下：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct &#123;</span><br><span class="line">    <span class="attribute">ngx_hash_t</span>            hash;</span><br><span class="line">    <span class="attribute">ngx_hash_wildcard_t</span>  *wc_head;</span><br><span class="line">    <span class="attribute">ngx_hash_wildcard_t</span>  *wc_tail;</span><br><span class="line">&#125; ngx_hash_combined_t;</span><br></pre></td></tr></table></figure><p>从其定义显见，该类型实际上包含了三个 hash 表，一个普通 hash 表，一个包含前向通配符的 hash 表和一个包含后向通配符的 hash 表。</p><p>Nginx 提供该类型的作用，在于提供一个方便的容器包含三个类型的 hash 表，当有包含通配符的和不包含通配符的一组 key 构建 hash 表以后，以一种方便的方式来查询，你不需要再考虑一个 key 到底是应该到哪个类型的 hash 表里去查了。</p><p>构造这样一组合 hash 表的时候，首先定义一个该类型的变量，再分别构造其包含的三个子 hash 表即可。</p><p>对于该类型 hash 表的查询，Nginx 提供了一个方便的函数 ngx_hash_find_combined。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key,</span><br><span class="line">u_char *name, size_t len);</span><br></pre></td></tr></table></figure><p>该函数在此组合 hash 表中，依次查询其三个子 hash 表，看是否匹配，一旦找到，立即返回查找结果，也就是说如果有多个可能匹配，则只返回第一个匹配的结果。</p><ul><li>hash: 此组合 hash 表对象。</li><li>key: 根据 name 计算出的 hash 值。</li><li>name: key 的具体内容。</li><li>len: name 的长度。</li></ul><p>返回查询的结果，未查到则返回 NULL。</p><h2 id="ngx-hash-keys-arrays-t"><a href="#ngx-hash-keys-arrays-t" class="headerlink" title="ngx_hash_keys_arrays_t"></a>ngx_hash_keys_arrays_t</h2><p>大家看到在构建一个 ngx_hash_wildcard_t 的时候，需要对通配符的哪些 key 进行预处理。这个处理起来比较麻烦。而当有一组 key，这些里面既有无通配符的 key，也有包含通配符的 key 的时候。我们就需要构建三个 hash 表，一个包含普通的 key 的 hash 表，一个包含前向通配符的 hash 表，一个包含后向通配符的 hash 表（或者也可以把这三个 hash 表组合成一个 ngx_hash_combined_t）。在这种情况下，为了让大家方便的构造这些 hash 表，Nginx 提供给了此辅助类型。</p><p>该类型以及相关的操作函数也定义在<code>src/core/ngx_hash.h|c</code>里。我们先来看一下该类型的定义。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct &#123;</span><br><span class="line">    <span class="attribute">ngx_uint_t</span>        hsize;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">ngx_pool_t</span>       *pool;</span><br><span class="line">    <span class="attribute">ngx_pool_t</span>       *temp_pool;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">ngx_array_t</span>       keys;</span><br><span class="line">    <span class="attribute">ngx_array_t</span>      *keys_hash;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">ngx_array_t</span>       dns_wc_head;</span><br><span class="line">    <span class="attribute">ngx_array_t</span>      *dns_wc_head_hash;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">ngx_array_t</span>       dns_wc_tail;</span><br><span class="line">    <span class="attribute">ngx_array_t</span>      *dns_wc_tail_hash;</span><br><span class="line">&#125; ngx_hash_keys_arrays_t;</span><br></pre></td></tr></table></figure><ul><li>hsize: 将要构建的 hash 表的桶的个数。对于使用这个结构中包含的信息构建的三种类型的 hash 表都会使用此参数。</li><li>pool: 构建这些 hash 表使用的 pool。</li><li>temp_pool: 在构建这个类型以及最终的三个 hash 表过程中可能用到临时 pool。该 temp_pool 可以在构建完成以后，被销毁掉。这里只是存放临时的一些内存消耗。</li><li>keys: 存放所有非通配符 key 的数组。</li><li>keys_hash: 这是个二维数组，第一个维度代表的是 bucket 的编号，那么 <code>keys_hash[i]</code> 中存放的是所有的 key 算出来的 hash 值对 hsize 取模以后的值为 i 的 key。假设有 3 个 key,分别是 key1,key2 和 key3 假设 hash 值算出来以后对 hsize 取模的值都是 i，那么这三个 key 的值就顺序存放在<code>keys_hash[i][0]</code>,<code>keys_hash[i][1]</code>, <code>keys_hash[i][2]</code>。该值在调用的过程中用来保存和检测是否有冲突的 key 值，也就是是否有重复。</li><li>dns_wc_head: 放前向通配符 key 被处理完成以后的值。比如：<code>\*.abc.com</code> 被处理完成以后，变成 “com.abc.” 被存放在此数组中。</li><li>dns_wc_tail: 存放后向通配符 key 被处理完成以后的值。比如：<code>mail.xxx.\*</code> 被处理完成以后，变成 “mail.xxx.” 被存放在此数组中。</li><li>dns_wc_head_hash: 该值在调用的过程中用来保存和检测是否有冲突的前向通配符的 key 值，也就是是否有重复。</li><li>dns_wc_tail_hash: 该值在调用的过程中用来保存和检测是否有冲突的后向通配符的 key 值，也就是是否有重复。</li></ul><p>在定义一个这个类型的变量，并对字段 pool 和 temp_pool 赋值以后，就可以调用函数 ngx_hash_add_key 把所有的 key 加入到这个结构中了，该函数会自动实现普通 key，带前向通配符的 key 和带后向通配符的 key 的分类和检查，并将这个些值存放到对应的字段中去，然后就可以通过检查这个结构体中的 keys、dns_wc_head、dns_wc_tail 三个数组是否为空，来决定是否构建普通 hash 表，前向通配符 hash 表和后向通配符 hash 表了（在构建这三个类型的 hash 表的时候，可以分别使用 keys、dns_wc_head、dns_wc_tail三个数组）。</p><p>构建出这三个 hash 表以后，可以组合在一个 ngx_hash_combined_t 对象中，使用 ngx_hash_find_combined 进行查找。或者是仍然保持三个独立的变量对应这三个 hash 表，自己决定何时以及在哪个 hash 表中进行查询。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_int_t</span> ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type);</span><br></pre></td></tr></table></figure><p>初始化这个结构，主要是对这个结构中的 ngx_array_t 类型的字段进行初始化，成功返回 NGX_OK。</p><ul><li>ha: 该结构的对象指针。</li><li>type: 该字段有 2 个值可选择，即 NGX_HASH_SMALL 和 NGX_HASH_LARGE。用来指明将要建立的 hash 表的类型，如果是 NGX_HASH_SMALL，则有比较小的桶的个数和数组元素大小。NGX_HASH_LARGE 则相反。</li></ul><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_int_t</span> ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key,</span><br><span class="line">void *value, ngx_uint_t flags);</span><br></pre></td></tr></table></figure><p>一般是循环调用这个函数，把一组键值对加入到这个结构体中。返回 NGX_OK 是加入成功。返回 NGX_BUSY 意味着key值重复。</p><ul><li>ha: 该结构的对象指针。</li><li>key: 参数名自解释了。</li><li>value: 参数名自解释了。</li><li>flags: 有两个标志位可以设置，NGX_HASH_WILDCARD_KEY 和 NGX_HASH_READONLY_KEY。同时要设置的使用逻辑与操作符就可以了。NGX_HASH_READONLY_KEY 被设置的时候，在计算 hash 值的时候，key 的值不会被转成小写字符，否则会。NGX_HASH_WILDCARD_KEY 被设置的时候，说明 key 里面可能含有通配符，会进行相应的处理。如果两个标志位都不设置，传 0。</li></ul><p>有关于这个数据结构的使用，可以参考<code>src/http/ngx_http.c</code>中的 ngx_http_server_names 函数。</p><h2 id="ngx-chain-t"><a href="#ngx-chain-t" class="headerlink" title="ngx_chain_t"></a>ngx_chain_t</h2><p>Nginx 的 filter 模块在处理从别的 filter 模块或者是 handler 模块传递过来的数据（实际上就是需要发送给客户端的 http response）。这个传递过来的数据是以一个链表的形式(ngx_chain_t)。而且数据可能被分多次传递过来。也就是多次调用 filter 的处理函数，以不同的 ngx_chain_t。</p><p>该结构被定义在<code>src/core/ngx_buf.h|c</code>。下面我们来看一下 ngx_chain_t 的定义。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct ngx_chain_s       ngx_chain_t;</span><br><span class="line"></span><br><span class="line"><span class="attribute">struct</span> ngx_chain_s &#123;</span><br><span class="line">    <span class="attribute">ngx_buf_t</span>    *buf;</span><br><span class="line">    <span class="attribute">ngx_chain_t</span>  *next;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>就 2 个字段，next 指向这个链表的下个节点。buf 指向实际的数据。所以在这个链表上追加节点也是非常容易，只要把末尾元素的 next 指针指向新的节点，把新节点的 next 赋值为 NULL 即可。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool);</span><br></pre></td></tr></table></figure><p>该函数创建一个 ngx_chain_t 的对象，并返回指向对象的指针，失败返回 NULL。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_free_chain(pool, cl)                                             \</span></span><br><span class="line">    cl-&gt;<span class="attribute">next</span> = pool-&gt;chain;                                                  \</span><br><span class="line">pool-&gt;<span class="attribute">chain</span> = cl</span><br></pre></td></tr></table></figure><p>该宏释放一个 ngx_chain_t 类型的对象。如果要释放整个 chain，则迭代此链表，对每个节点使用此宏即可。</p><p><strong>注意:</strong> 对 ngx_chaint_t 类型的释放，并不是真的释放了内存，而仅仅是把这个对象挂在了这个 pool 对象的一个叫做 chain 的字段对应的 chain 上，以供下次从这个 pool 上分配 ngx_chain_t 类型对象的时候，快速的从这个 pool-&gt;chain上 取下链首元素就返回了，当然，如果这个链是空的，才会真的在这个 pool 上使用 ngx_palloc 函数进行分配。</p><h2 id="ngx-buf-t"><a href="#ngx-buf-t" class="headerlink" title="ngx_buf_t"></a>ngx_buf_t</h2><p>这个 ngx_buf_t 就是这个 ngx_chain_t 链表的每个节点的实际数据。该结构实际上是一种抽象的数据结构，它代表某种具体的数据。这个数据可能是指向内存中的某个缓冲区，也可能指向一个文件的某一部分，也可能是一些纯元数据（元数据的作用在于指示这个链表的读取者对读取的数据进行不同的处理）。</p><p>该数据结构位于<code>src/core/ngx_buf.h|c</code>文件中。我们来看一下它的定义。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">struct</span> ngx_buf_s &#123;</span><br><span class="line">    <span class="attribute">u_char</span>          *pos;</span><br><span class="line">    <span class="attribute">u_char</span>          *<span class="literal">last</span>;</span><br><span class="line">    <span class="attribute">off_t</span>            file_pos;</span><br><span class="line">    <span class="attribute">off_t</span>            file_last;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">u_char</span>          *start;         /* <span class="attribute">start</span> of buffer */</span><br><span class="line">    u_char          *end;           /* <span class="attribute">end</span> of buffer */</span><br><span class="line">    ngx_buf_tag_t    tag;</span><br><span class="line">    <span class="attribute">ngx_file_t</span>      *file;</span><br><span class="line">    <span class="attribute">ngx_buf_t</span>       *shadow;</span><br><span class="line"></span><br><span class="line">    /* <span class="attribute">the</span> buf<span class="string">&#x27;s content could be changed */</span></span><br><span class="line"><span class="string">    unsigned         temporary:1;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    /*</span></span><br><span class="line"><span class="string">     * the buf&#x27;</span>s content is in a memory cache or in a read only memory</span><br><span class="line">     * and must not be changed</span><br><span class="line">     */</span><br><span class="line">    unsigned         memory:<span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    /* <span class="attribute">the</span> buf<span class="string">&#x27;s content is mmap()ed and must not be changed */</span></span><br><span class="line"><span class="string">    unsigned         mmap:1;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    unsigned         recycled:1;</span></span><br><span class="line"><span class="string">    unsigned         in_file:1;</span></span><br><span class="line"><span class="string">    unsigned         flush:1;</span></span><br><span class="line"><span class="string">    unsigned         sync:1;</span></span><br><span class="line"><span class="string">    unsigned         last_buf:1;</span></span><br><span class="line"><span class="string">    unsigned         last_in_chain:1;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    unsigned         last_shadow:1;</span></span><br><span class="line"><span class="string">    unsigned         temp_file:1;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    /* STUB */ int   num;</span></span><br><span class="line"><span class="string">&#125;;</span></span><br></pre></td></tr></table></figure><ul><li>pos：当 buf 所指向的数据在内存里的时候，pos 指向的是这段数据开始的位置。</li><li>last：当 buf 所指向的数据在内存里的时候，last 指向的是这段数据结束的位置。</li><li>file_pos：当 buf 所指向的数据是在文件里的时候，file_pos 指向的是这段数据的开始位置在文件中的偏移量。</li><li>file_last：当 buf 所指向的数据是在文件里的时候，file_last 指向的是这段数据的结束位置在文件中的偏移量。</li><li>start：当 buf 所指向的数据在内存里的时候，这一整块内存包含的内容可能被包含在多个 buf 中(比如在某段数据中间插入了其他的数据，这一块数据就需要被拆分开)。那么这些 buf 中的 start 和 end 都指向这一块内存的开始地址和结束地址。而 pos 和 last 指向本 buf 所实际包含的数据的开始和结尾。</li><li>end：解释参见 start。</li><li>tag：实际上是一个<code>void *</code>类型的指针，使用者可以关联任意的对象上去，只要对使用者有意义。</li><li>file：当 buf 所包含的内容在文件中时，file字段指向对应的文件对象。</li><li>shadow：当这个 buf 完整 copy 了另外一个 buf 的所有字段的时候，那么这两个 buf 指向的实际上是同一块内存，或者是同一个文件的同一部分，此时这两个 buf 的 shadow 字段都是指向对方的。那么对于这样的两个 buf，在释放的时候，就需要使用者特别小心，具体是由哪里释放，要提前考虑好，如果造成资源的多次释放，可能会造成程序崩溃！</li><li>temporary：为 1 时表示该 buf 所包含的内容是在一个用户创建的内存块中，并且可以被在 filter 处理的过程中进行变更，而不会造成问题。</li><li>memory：为 1 时表示该 buf 所包含的内容是在内存中，但是这些内容却不能被进行处理的 filter 进行变更。</li><li>mmap：为 1 时表示该 buf 所包含的内容是在内存中, 是通过 mmap 使用内存映射从文件中映射到内存中的，这些内容却不能被进行处理的 filter 进行变更。</li><li>recycled：可以回收的。也就是这个 buf 是可以被释放的。这个字段通常是配合 shadow 字段一起使用的，对于使用 ngx_create_temp_buf 函数创建的 buf，并且是另外一个 buf 的 shadow，那么可以使用这个字段来标示这个buf是可以被释放的。</li><li>in_file：为 1 时表示该 buf 所包含的内容是在文件中。</li><li>flush：遇到有 flush 字段被设置为 1 的 buf 的 chain，则该 chain 的数据即便不是最后结束的数据（last_buf被设置，标志所有要输出的内容都完了），也会进行输出，不会受 postpone_output 配置的限制，但是会受到发送速率等其他条件的限制。</li><li>last_buf：数据被以多个 chain 传递给了过滤器，此字段为 1 表明这是最后一个 buf。</li><li>last_in_chain：在当前的 chain 里面，此 buf 是最后一个。特别要注意的是 last_in_chain 的 buf 不一定是last_buf，但是 last_buf 的 buf 一定是 last_in_chain 的。这是因为数据会被以多个 chain 传递给某 个filter 模块。</li><li>last_shadow：在创建一个 buf 的 shadow 的时候，通常将新创建的一个 buf 的 last_shadow 置为 1。</li><li>temp_file:由于受到内存使用的限制，有时候一些 buf 的内容需要被写到磁盘上的临时文件中去，那么这时，就设置此标志。</li></ul><p>对于此对象的创建，可以直接在某个 ngx_pool_t 上分配，然后根据需要，给对应的字段赋值。也可以使用定义好的 2 个宏：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_alloc_buf(pool)  ngx_palloc(pool, sizeof(ngx_buf_t))</span></span><br><span class="line"><span class="comment">#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))</span></span><br></pre></td></tr></table></figure><p>这两个宏使用类似函数，也是不说自明的。</p><p>对于创建 temporary 字段为 1 的 buf（就是其内容可以被后续的 filter 模块进行修改），可以直接使用函数 ngx_create_temp_buf 进行创建。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_buf_t</span> *ngx_create_temp_buf(ngx_pool_t *pool, size_t size);</span><br></pre></td></tr></table></figure><p>该函数创建一个 ngx_buf_t 类型的对象，并返回指向这个对象的指针，创建失败返回 NULL。</p><p>对于创建的这个对象，它的 start 和 end 指向新分配内存开始和结束的地方。pos 和 last 都指向这块新分配内存的开始处，这样，后续的操作可以在这块新分配的内存上存入数据。</p><ul><li>pool: 分配该 buf 和 buf 使用的内存所使用的 pool。</li><li>size: 该 buf 使用的内存的大小。</li></ul><p>为了配合对 ngx_buf_t 的使用，Nginx 定义了以下的宏方便操作。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_buf_in_memory(b)        (b-&gt;temporary || b-&gt;memory || b-&gt;mmap)</span></span><br></pre></td></tr></table></figure><p>返回这个 buf 里面的内容是否在内存里。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_buf_in_memory_only(b)   (ngx_buf_in_memory(b) &amp;&amp; !b-&gt;in_file)</span></span><br></pre></td></tr></table></figure><p>返回这个 buf 里面的内容是否仅仅在内存里，并且没有在文件里。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_buf_special(b)                                                   \</span></span><br><span class="line">    ((b-&gt;<span class="attribute">flush</span> || b-&gt;last_buf || b-&gt;sync)                                    \</span><br><span class="line">     &amp;&amp; !ngx_buf_in_memory(b) &amp;&amp; !b-&gt;in_file)</span><br></pre></td></tr></table></figure><p>返回该 buf 是否是一个特殊的 buf，只含有特殊的标志和没有包含真正的数据。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_buf_sync_only(b)                                                 \</span></span><br><span class="line">    (b-&gt;<span class="attribute">sync</span>                                                                 \</span><br><span class="line">     &amp;&amp; !ngx_buf_in_memory(b) &amp;&amp; !b-&gt;in_file &amp;&amp; !b-&gt;flush &amp;&amp; !b-&gt;last_buf)</span><br></pre></td></tr></table></figure><p>返回该 buf 是否是一个只包含 sync 标志而不包含真正数据的特殊 buf。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_buf_size(b)                                                      \</span></span><br><span class="line">    (ngx_buf_in_memory(b) ? (off_t) (b-&gt;<span class="attribute">last</span> - b-&gt;pos):                      \</span><br><span class="line">                            (b-&gt;file_last - b-&gt;file_pos))</span><br></pre></td></tr></table></figure><p>返回该 buf 所含数据的大小，不管这个数据是在文件里还是在内存里。</p><h2 id="ngx-list-t"><a href="#ngx-list-t" class="headerlink" title="ngx_list_t"></a>ngx_list_t</h2><p>ngx_list_t 顾名思义，看起来好像是一个 list 的数据结构。这样的说法，算对也不算对。因为它符合 list 类型数据结构的一些特点，比如可以添加元素，实现自增长，不会像数组类型的数据结构，受到初始设定的数组容量的限制，并且它跟我们常见的 list 型数据结构也是一样的，内部实现使用了一个链表。</p><p>那么它跟我们常见的链表实现的 list 有什么不同呢？不同点就在于它的节点，它的节点不像我们常见的 list 的节点，只能存放一个元素，ngx_list_t 的节点实际上是一个固定大小的数组。</p><p>在初始化的时候，我们需要设定元素需要占用的空间大小，每个节点数组的容量大小。在添加元素到这个 list 里面的时候，会在最尾部的节点里的数组上添加元素，如果这个节点的数组存满了，就再增加一个新的节点到这个 list 里面去。</p><p>好了，看到这里，大家应该基本上明白这个 list 结构了吧？还不明白也没有关系，下面我们来具体看一下它的定义，这些定义和相关的操作函数定义在<code>src/core/ngx_list.h|c</code>文件中。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct &#123;</span><br><span class="line">    <span class="attribute">ngx_list_part_t</span>  *<span class="literal">last</span>;</span><br><span class="line">    <span class="attribute">ngx_list_part_t</span>   part;</span><br><span class="line">    <span class="attribute">size_t</span>            size;</span><br><span class="line">    <span class="attribute">ngx_uint_t</span>        nalloc;</span><br><span class="line">    <span class="attribute">ngx_pool_t</span>       *pool;</span><br><span class="line">&#125; ngx_list_t;</span><br></pre></td></tr></table></figure><ul><li>last: 指向该链表的最后一个节点。</li><li>part: 该链表的首个存放具体元素的节点。</li><li>size: 链表中存放的具体元素所需内存大小。</li><li>nalloc: 每个节点所含的固定大小的数组的容量。</li><li>pool: 该 list 使用的分配内存的 pool。</li></ul><p>好，我们在看一下每个节点的定义。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct ngx_list_part_s  ngx_list_part_t;</span><br><span class="line"><span class="attribute">struct</span> ngx_list_part_s &#123;</span><br><span class="line">    <span class="attribute">void</span>             *elts;</span><br><span class="line">    <span class="attribute">ngx_uint_t</span>        nelts;</span><br><span class="line">    <span class="attribute">ngx_list_part_t</span>  *next;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>elts: 节点中存放具体元素的内存的开始地址。</li><li>nelts: 节点中已有元素个数。这个值是不能大于链表头节点 ngx_list_t 类型中的 nalloc 字段的。</li><li>next: 指向下一个节点。</li></ul><p>我们来看一下提供的一个操作的函数。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_list_t</span> *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);</span><br></pre></td></tr></table></figure><p>该函数创建一个 ngx_list_t 类型的对象，并对该 list 的第一个节点分配存放元素的内存空间。</p><ul><li>pool: 分配内存使用的 pool。</li><li>n: 每个节点（ngx_list_part_t）固定长度的数组的长度，即最多可以存放的元素个数。</li><li>size: 每个元素所占用的内存大小。</li><li>返回值: 成功返回指向创建的 ngx_list_t 对象的指针，失败返回 NULL。</li></ul><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">void</span> *ngx_list_push(ngx_list_t *list);</span><br></pre></td></tr></table></figure><p>该函数在给定的 list 的尾部追加一个元素，并返回指向新元素存放空间的指针。如果追加失败，则返回 NULL。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">static</span> ngx_inline ngx_int_t</span><br><span class="line">ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size);</span><br></pre></td></tr></table></figure><p>该函数是用于 ngx_list_t 类型的对象已经存在，但是其第一个节点存放元素的内存空间还未分配的情况下，可以调用此函数来给这个 list 的首节点来分配存放元素的内存空间。</p><p>那么什么时候会出现已经有了 ngx_list_t 类型的对象，而其首节点存放元素的内存尚未分配的情况呢？那就是这个 ngx_list_t 类型的变量并不是通过调用 ngx_list_create 函数创建的。例如：如果某个结构体的一个成员变量是 ngx_list_t 类型的，那么当这个结构体类型的对象被创建出来的时候，这个成员变量也被创建出来了，但是它的首节点的存放元素的内存并未被分配。</p><p>总之，如果这个 ngx_list_t 类型的变量，如果不是你通过调用函数 ngx_list_create 创建的，那么就必须调用此函数去初始化，否则，你往这个 list 里追加元素就可能引发不可预知的行为，亦或程序会崩溃!</p><h2 id="ngx-queue-t"><a href="#ngx-queue-t" class="headerlink" title="ngx_queue_t"></a>ngx_queue_t</h2><p>ngx_queue_t 是 Nginx 中的双向链表，在 Nginx 源码目录<code>src/core</code>下面的<code>ngx_queue.h|c</code>里面。它的原型如下：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct ngx_queue_s ngx_queue_t;</span><br><span class="line"></span><br><span class="line"><span class="attribute">struct</span> ngx_queue_s &#123;</span><br><span class="line">    <span class="attribute">ngx_queue_t</span>  *prev;</span><br><span class="line">    <span class="attribute">ngx_queue_t</span>  *next;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>不同于教科书中将链表节点的数据成员声明在链表节点的结构体中，ngx_queue_t 只是声明了前向和后向指针。在使用的时候，我们首先需要定义一个哨兵节点(对于后续具体存放数据的节点，我们称之为数据节点)，比如：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_queue_t</span> free;</span><br></pre></td></tr></table></figure><p>接下来需要进行初始化，通过宏 ngx_queue_init()来实现：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ngx_queue_init(&amp;free);</span><br></pre></td></tr></table></figure><p>ngx_queue_init()的宏定义如下：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_queue_init(q)     \</span></span><br><span class="line">    (q)-&gt;<span class="attribute">prev</span> = q;            \</span><br><span class="line">    (q)-&gt;<span class="attribute">next</span> = q</span><br></pre></td></tr></table></figure><p>可见初始的时候哨兵节点的 prev 和 next 都指向自己，因此其实是一个空链表。ngx_queue_empty()可以用来判断一个链表是否为空，其实现也很简单，就是：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_queue_empty(h)    \</span></span><br><span class="line">    (<span class="attribute">h</span> == (h)-&gt;prev)</span><br></pre></td></tr></table></figure><p>那么如何声明一个具有数据元素的链表节点呢？只要在相应的结构体中加上一个 ngx_queue_t 的成员就行了。比如 ngx_http_upstream_keepalive_module 中的 ngx_http_upstream_keepalive_cache_t：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">typedef</span> struct &#123;</span><br><span class="line">    <span class="attribute">ngx_http_upstream_keepalive_srv_conf_t</span>  *conf;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">ngx_queue_t</span>                        queue;</span><br><span class="line">    <span class="attribute">ngx_connection_t</span>                  *connection;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">socklen_t</span>                          socklen;</span><br><span class="line">    <span class="attribute">u_char</span>                             sockaddr[NGX_SOCKADDRLEN];</span><br><span class="line">&#125; ngx_http_upstream_keepalive_cache_t;</span><br></pre></td></tr></table></figure><p>对于每一个这样的数据节点，可以通过 ngx_queue_insert_head()来添加到链表中，第一个参数是哨兵节点，第二个参数是数据节点，比如：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_http_upstream_keepalive_cache_t</span> cache;</span><br><span class="line">ngx_queue_insert_head(&amp;free, &amp;cache.queue);</span><br></pre></td></tr></table></figure><p>相应的几个宏定义如下：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_queue_insert_head(h, x)                         \</span></span><br><span class="line">    (x)-&gt;<span class="attribute">next</span> = (h)-&gt;next;                                  \</span><br><span class="line">    (x)-&gt;next-&gt;<span class="attribute">prev</span> = x;                                    \</span><br><span class="line">    (x)-&gt;<span class="attribute">prev</span> = h;                                          \</span><br><span class="line">    (h)-&gt;<span class="attribute">next</span> = x</span><br><span class="line"></span><br><span class="line"><span class="comment">#define ngx_queue_insert_after   ngx_queue_insert_head</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#define ngx_queue_insert_tail(h, x)                          \</span></span><br><span class="line">    (x)-&gt;prev = (h)-&gt;prev;                                   \</span><br><span class="line">    (x)-&gt;prev-&gt;<span class="attribute">next</span> = x;                                     \</span><br><span class="line">    (x)-&gt;<span class="attribute">next</span> = h;                                           \</span><br><span class="line">    (h)-&gt;<span class="attribute">prev</span> = x</span><br></pre></td></tr></table></figure><p>ngx_queue_insert_head() 和 ngx_queue_insert_after() 都是往头部添加节点，ngx_queue_insert_tail() 是往尾部添加节点。从代码可以看出哨兵节点的 prev 指向链表的尾数据节点，next 指向链表的头数据节点。另外 ngx_queue_head() 和 ngx_queue_last() 这两个宏分别可以得到头节点和尾节点。</p><p>那假如现在有一个 <code>ngx_queue_t *q</code> 指向的是链表中的数据节点的 queue 成员，如何得到ngx_http_upstream_keepalive_cache_t 的数据呢？ Nginx 提供了 ngx_queue_data() 宏来得到ngx_http_upstream_keepalive_cache_t 的指针，例如：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ngx_http_upstream_keepalive_cache_t</span> *cache = ngx_queue_data(q,</span><br><span class="line">                             ngx_http_upstream_keepalive_cache_t,</span><br><span class="line">                                                    queue);</span><br></pre></td></tr></table></figure><p>也许您已经可以猜到 ngx_queue_data 是通过地址相减来得到的：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#define ngx_queue_data(q, type, link)                        \</span></span><br><span class="line">    (<span class="attribute">type</span> *) ((u_char *) q - offsetof(type, link))</span><br></pre></td></tr></table></figure><p>另外 Nginx 也提供了 ngx_queue_remove()宏来从链表中删除一个数据节点，以及 ngx_queue_add() 用来将一个链表添加到另一个链表。</p></article><div class="post-copyright"><div class="copyright-cc-box"><i class="anzhiyufont anzhiyu-icon-copyright"></i></div><div class="post-copyright__author_box"><a class="post-copyright__author_img" href="/" title="头像"><img class="post-copyright__author_img_back" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"><img class="post-copyright__author_img_front" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"></a><div class="post-copyright__author_name">云少</div><div class="post-copyright__author_desc">站在巨人的肩膀罢了</div></div><div class="post-copyright__post__info"><a class="post-copyright__original" title="该文章为原创文章，注意版权协议" href="https://it985.github.io/posts/c504cd62.html">原创</a><a class="post-copyright-title"><span onclick='rm.copyPageUrl("https://it985.github.io/posts/c504cd62.html")'>六、Nginx 基本数据结构</span></a></div><div class="post-tools" id="post-tools"><div class="post-tools-left"><div class="rewardLeftButton"><div class="post-reward" onclick="anzhiyu.addRewardMask()"><div class="reward-button button--animated" title="赞赏作者"><i class="anzhiyufont anzhiyu-icon-hand-heart-fill"></i>打赏作者</div><div class="reward-main"><div class="reward-all"><span class="reward-title">感谢你赐予我前进的力量</span><ul class="reward-group"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/wxpay.webp" alt="wechat"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/alipay.webp" alt="alipay"></a><div class="post-qr-code-desc">alipay</div></li></ul><a class="reward-main-btn" href="/about/#about-reward" target="_blank"><div class="reward-text">赞赏者名单</div><div class="reward-dec">因为你们的支持让我意识到写文章的价值🙏</div></a></div></div></div><div id="quit-box" onclick="anzhiyu.removeRewardMask()" style="display:none"></div></div><div class="shareRight"><div class="share-link mobile"><div class="share-qrcode"><div class="share-button" title="使用手机访问这篇文章"><i class="anzhiyufont anzhiyu-icon-qrcode"></i></div><div class="share-main"><div class="share-main-all"><div id="qrcode" title="https://it985.github.io/posts/c504cd62.html"></div><div class="reward-dec">使用手机访问这篇文章</div></div></div></div></div><div class="share-link weibo"><a class="share-button" target="_blank" href="https://service.weibo.com/share/share.php?title=六、Nginx 基本数据结构&amp;url=https://it985.github.io/posts/c504cd62.html&amp;pic=https://img.xjh.me/random_img.php?type=bg&amp;ctype=nature&amp;return=302&amp;_r_=b4b7cfea-fdef-8d0f-cb5d-61fdc2167c9f" rel="external nofollow noreferrer noopener"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a></div><div class="share-link copyurl"><div class="share-button" id="post-share-url" title="复制链接" onclick="rm.copyPageUrl()"><i class="anzhiyufont anzhiyu-icon-link"></i></div></div></div></div></div><div class="post-copyright__notice"><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://it985.github.io" target="_blank">云少IT</a>！</span></div></div><div class="post-tools-right"><div class="tag_share"><div class="post-meta__box"><div class="post-meta__box__tag-list"></div></div><div class="post_share"><div class="social-share" data-image="https://img02.anheyu.com/adminuploads/1/2022/09/05/6315e146a8bbd.webp" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/css/share.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/js/social-share.min.js" defer></script></div></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/posts/61738bf9.html"><img class="prev-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.bing.com/th?id=OHR.SeonamTemple_EN-US8180397216_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">五、Nginx 基础概念</div></div></a></div><div class="next-post pull-right"><a href="/posts/b16e9477.html"><img class="next-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.bing.com/th?id=OHR.MuseumMile_EN-US8035796645_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">七、Nginx 的配置系统</div></div></a></div></nav><hr><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="anzhiyufont anzhiyu-icon-comments"></i><span> 评论</span></div><div class="comment-randomInfo"><a onclick="anzhiyu.addRandomCommentInfo()" href="javascript:void(0)" rel="external nofollow noreferrer">匿名评论</a><a href="/privacy" style="margin-left:4px">隐私政策</a></div><div class="comment-switch"><span class="first-comment">Twikoo</span><span id="switch-btn"></span><span class="second-comment">Artalk</span></div><div class="comment-tips" id="comment-tips"><span>✅ 你无需删除空行，直接评论以获取最佳展示效果</span></div></div><div class="comment-wrap"><div><div id="twikoo-wrap"></div></div><div><div id="artalk-wrap"></div></div></div></div><div class="comment-barrage"></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-bars"></i><span>文章目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%85%AD%E3%80%81Nginx-%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84"><span class="toc-number">1.</span> <span class="toc-text">六、Nginx 基本数据结构</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-str-t"><span class="toc-number">1.1.</span> <span class="toc-text">ngx_str_t</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-pool-t"><span class="toc-number">1.2.</span> <span class="toc-text">ngx_pool_t</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-array-t"><span class="toc-number">1.3.</span> <span class="toc-text">ngx_array_t</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-hash-t"><span class="toc-number">1.4.</span> <span class="toc-text">ngx_hash_t</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-hash-wildcard-t"><span class="toc-number">1.5.</span> <span class="toc-text">ngx_hash_wildcard_t</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-hash-combined-t"><span class="toc-number">1.6.</span> <span class="toc-text">ngx_hash_combined_t</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-hash-keys-arrays-t"><span class="toc-number">1.7.</span> <span class="toc-text">ngx_hash_keys_arrays_t</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-chain-t"><span class="toc-number">1.8.</span> <span class="toc-text">ngx_chain_t</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-buf-t"><span class="toc-number">1.9.</span> <span class="toc-text">ngx_buf_t</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-list-t"><span class="toc-number">1.10.</span> <span class="toc-text">ngx_list_t</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ngx-queue-t"><span class="toc-number">1.11.</span> <span class="toc-text">ngx_queue_t</span></a></li></ol></li></ol></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div id="footer_deal"><a class="deal_link" href="mailto:2071916845@qq.com" rel="external nofollow noreferrer" title="email"><i class="anzhiyufont anzhiyu-icon-envelope"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://weibo.com/" title="微博"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.facebook.com/" title="facebook"><i class="anzhiyufont anzhiyu-icon-facebook1"></i></a><a class="deal_link" href="/atom.xml" title="RSS"><i class="anzhiyufont anzhiyu-icon-rss"></i></a><img class="footer_mini_logo" title="返回顶部" alt="返回顶部" onclick="anzhiyu.scrollToDest(0,500)" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" size="50px"><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/it985" title="Github"><i class="anzhiyufont anzhiyu-icon-github"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://space.bilibili.com/300767383" title="Bilibili"><i class="anzhiyufont anzhiyu-icon-bilibili"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://v.douyin.com/" title="抖音"><i class="anzhiyufont anzhiyu-icon-tiktok"></i></a><a class="deal_link" href="/copyright" title="CC"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i></a></div><div id="anzhiyu-footer"><div class="footer-group"><div class="footer-title">服务</div><div class="footer-links"><a class="footer-item" title="51la统计" target="_blank" rel="noopener external nofollow noreferrer" href="https://v6.51.la/">51la统计</a><a class="footer-item" title="十年之约" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.foreverblog.cn/">十年之约</a><a class="footer-item" title="开往" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/travellings-link/travellings">开往</a></div></div><div class="footer-group"><div class="footer-title">导航</div><div class="footer-links"><a class="footer-item" title="即刻短文" href="/essay/">即刻短文</a><a class="footer-item" title="友链文章" href="/fcircle/">友链文章</a><a class="footer-item" title="留言板" href="/comments/">留言板</a></div></div><div class="footer-group"><div class="footer-title">协议</div><div class="footer-links"><a class="footer-item" title="免责声明" href="/disclaimer/">免责声明</a><a class="footer-item" title="隐私协议" href="/privacy/">隐私协议</a><a class="footer-item" title="Cookies" href="/cookies/">Cookies</a><a class="footer-item" title="版权协议" href="/copyright/">版权协议</a></div></div><div class="footer-group"><div class="footer-title">娱乐</div><div class="footer-links"><a class="footer-item" title="小空调" href="/air-conditioner/">小空调</a><a class="footer-item" title="围住小猫" href="/catch-cat/">围住小猫</a><a class="footer-item" title="免费图床" href="/huluxiapicture/">免费图床</a></div></div><div class="footer-group"><div class="footer-title">推荐分类</div><div class="footer-links"><a class="footer-item" title="Java" href="/categories/Java/">Java</a><a class="footer-item" title="面试" href="/categories/%E9%9D%A2%E8%AF%95/">面试</a></div></div><div class="footer-group"><div class="footer-title-group"><div class="footer-title">友链</div><a class="random-friends-btn" id="footer-random-friends-btn" href="javascript:addFriendLinksInFooter();" rel="external nofollow noreferrer" title="换一批友情链接"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i></a></div><div class="footer-links" id="friend-links-in-footer"></div></div></div><div id="workboard"><img class="workSituationImg boardsign" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-上班摸鱼中.svg" alt="距离月入25k也就还差一个大佬带我~" title="距离月入25k也就还差一个大佬带我~"><div id="runtimeTextTip"></div></div><div class="wordcount"></div><span>云少已经写了 996.7k 字，</span><span>好像写完一本我国著名的 四大名著 了！！！</span><p id="ghbdages"><a class="github-badge" target="_blank" href="https://hexo.io/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="博客框架为Hexo_v5.4.0" title="博客框架为Hexo_v5.4.0"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Frame-Hexo.svg" alt="博客框架为Hexo_v5.4.0"></a><a class="github-badge" target="_blank" href="https://www.upyun.com/?utm_source=lianmeng&amp;utm_medium=referral" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站使用又拍云为静态资源提供CDN加速" title="本站使用又拍云为静态资源提供CDN加速"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/CDN-%E5%8F%88%E6%8B%8D%E4%BA%91-orange%3Fstyle%3Dflat%26logo%3D%E5%8F%88%E6%8B%8D%E4%BA%91" alt="本站使用又拍云为静态资源提供CDN加速"></a><a class="github-badge" target="_blank" href="https://github.com/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站项目由Github托管" title="本站项目由Github托管"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Source-Github.svg" alt="本站项目由Github托管"></a><a class="github-badge" target="_blank" href="http://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可" title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.2.0/img/badge/Copyright-BY-NC-SA.svg" alt="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"></a><a class="github-badge" target="_blank" href="https://icp.gov.moe/?keyword=20221607" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="萌ICP备20221607号" title="萌ICP备20221607号"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/%E8%90%8CICP%E5%A4%87-20221607-fe1384?style-flat&amp;logo=" alt="萌ICP备20221607号"></a></p></div><div id="footer-bar"><div class="footer-bar-links"><div class="footer-bar-left"><div id="footer-bar-tips"><div class="copyright">&copy;2018 - 2023 By <a class="footer-bar-link" href="/" title="云少" target="_blank">云少</a></div></div><div id="footer-type-tips"></div><div class="js-pjax"><script>function subtitleType(){fetch("https://v1.hitokoto.cn").then(t=>t.json()).then(t=>{var e="出自 "+t.from,p=[];p.unshift(t.hitokoto,e),window.typed=new Typed("#footer-type-tips",{strings:p,startDelay:300,typeSpeed:150,loop:!0,backSpeed:50})})}"function"==typeof Typed?subtitleType():getScript("https://cdn.cbd.int/typed.js@2.0.15/dist/typed.umd.js").then(subtitleType)</script></div></div><div class="footer-bar-right"><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="云少">云少</a><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://beian.miit.gov.cn/" title="鄂ICP备2021021095号-2">鄂ICP备2021021095号-2</a><a class="footer-bar-link cc" href="/copyright" title="cc协议"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-by-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nc-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nd-line"></i></a></div></div></div></footer></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="sidebar-site-data site-data is-center"><a href="/archives/" title="archive"><div class="headline">文章</div><div class="length-num">861</div></a><a href="/tags/" title="tag"><div class="headline">标签</div><div class="length-num">35</div></a><a href="/categories/" title="category"><div class="headline">分类</div><div class="length-num">6</div></a></div><span class="sidebar-menu-item-title">功能</span><div class="sidebar-menu-item"><a class="darkmode_switchbutton menu-child" href="javascript:void(0);" rel="external nofollow noreferrer" title="显示模式"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span>显示模式</span></a></div><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div><span class="sidebar-menu-item-title">标签</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:.88rem;color:#758692">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:.88rem;color:#2584bf">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:.88rem;color:#bfad3e">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:.88rem;color:#b9b609">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:.88rem;color:#14c83c">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:.88rem;color:#0a1878">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:.88rem;color:#625421">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:.88rem;color:#aa71bf">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:.88rem;color:#ad8774">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:.88rem;color:#19704c">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:.88rem;color:#7e5c78">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:.88rem;color:#700769">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:.88rem;color:#2b8671">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:.88rem;color:#1b0694">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:.88rem;color:#36202d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:.88rem;color:#643f76">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:.88rem;color:#1e4c38">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:.88rem;color:#a5a097">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:.88rem;color:#05682d">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:.88rem;color:#38ad2a">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:.88rem;color:#827a6b">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:.88rem;color:#4a3ac7">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:.88rem;color:#b89c14">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:.88rem;color:#922401">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:.88rem;color:#437d09">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:.88rem;color:#705b06">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:.88rem;color:#06a544">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:.88rem;color:#c86c4f">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:.88rem;color:#560b2c">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:.88rem;color:#4e0a32">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:.88rem;color:#230f8e">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:.88rem;color:#c0771e">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:.88rem;color:#b40caa">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:.88rem;color:#67389a">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:.88rem;color:#57425c">面试<sup>11</sup></a></div></div><hr></div></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="anzhiyufont anzhiyu-icon-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="anzhiyufont anzhiyu-icon-gear"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="anzhiyufont anzhiyu-icon-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="anzhiyufont anzhiyu-icon-comments"></i></a><button type="button" title="切换背景" onclick="toggleWinbox()"><i class="fas fa-display"></i></button><button id="go-up" type="button" title="回到顶部"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></button></div></div><div id="algolia-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><button class="search-close-button"><i class="anzhiyufont anzhiyu-icon-xmark"></i></button></nav><div class="search-wrap"><div id="algolia-search-input"></div><hr><div id="algolia-search-results"><div id="algolia-hits"></div><div id="algolia-pagination"></div><div id="algolia-info"><div class="algolia-stats"></div><div class="algolia-poweredBy"></div></div></div></div></div><div id="search-mask"></div></div><div id="rightMenu"><div class="rightMenu-group rightMenu-small"><div class="rightMenu-item" id="menu-backward"><i class="anzhiyufont anzhiyu-icon-arrow-left"></i></div><div class="rightMenu-item" id="menu-forward"><i class="anzhiyufont anzhiyu-icon-arrow-right"></i></div><div class="rightMenu-item" id="menu-refresh"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right" style="font-size:1rem"></i></div><div class="rightMenu-item" id="menu-top"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></div></div><div class="rightMenu-group rightMenu-line rightMenuPlugin"><div class="rightMenu-item" id="menu-copytext"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制选中文本</span></div><div class="rightMenu-item" id="menu-pastetext"><i class="anzhiyufont anzhiyu-icon-paste"></i><span>粘贴文本</span></div><a class="rightMenu-item" id="menu-commenttext"><i class="anzhiyufont anzhiyu-icon-comment-medical"></i><span>引用到评论</span></a><div class="rightMenu-item" id="menu-newwindow"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开</span></div><div class="rightMenu-item" id="menu-copylink"><i class="anzhiyufont anzhiyu-icon-link"></i><span>复制链接地址</span></div><div class="rightMenu-item" id="menu-copyimg"><i class="anzhiyufont anzhiyu-icon-images"></i><span>复制此图片</span></div><div class="rightMenu-item" id="menu-downloadimg"><i class="anzhiyufont anzhiyu-icon-download"></i><span>下载此图片</span></div><div class="rightMenu-item" id="menu-newwindowimg"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开图片</span></div><div class="rightMenu-item" id="menu-search"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>站内搜索</span></div><div class="rightMenu-item" id="menu-searchBaidu"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>百度搜索</span></div><div class="rightMenu-item" id="menu-music-toggle"><i class="anzhiyufont anzhiyu-icon-play"></i><span>播放音乐</span></div><div class="rightMenu-item" id="menu-music-back"><i class="anzhiyufont anzhiyu-icon-backward"></i><span>切换到上一首</span></div><div class="rightMenu-item" id="menu-music-forward"><i class="anzhiyufont anzhiyu-icon-forward"></i><span>切换到下一首</span></div><div class="rightMenu-item" id="menu-music-playlist" onclick="window.open(&quot;https://y.qq.com/n/ryqq/playlist/8802438608&quot;, &quot;_blank&quot;);" style="display:none"><i class="anzhiyufont anzhiyu-icon-radio"></i><span>查看所有歌曲</span></div><div class="rightMenu-item" id="menu-music-copyMusicName"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制歌名</span></div></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item menu-link" id="menu-randomPost"><i class="anzhiyufont anzhiyu-icon-shuffle"></i><span>随便逛逛</span></a><a class="rightMenu-item menu-link" href="/categories/"><i class="anzhiyufont anzhiyu-icon-cube"></i><span>博客分类</span></a><a class="rightMenu-item menu-link" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags"></i><span>文章标签</span></a></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item" id="menu-copy" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制地址</span></a><a class="rightMenu-item" id="menu-commentBarrage" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-message"></i><span class="menu-commentBarrage-text">关闭热评</span></a><a class="rightMenu-item" id="menu-darkmode" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span class="menu-darkmode-text">深色模式</span></a><a class="rightMenu-item" id="menu-translate" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-language"></i><span>轉為繁體</span></a></div></div><div id="rightmenu-mask"></div><div id="he-plugin-simple"></div><script>var WIDGET={CONFIG:{modules:"0124",background:"2",tmpColor:"FFFFFF",tmpSize:"16",cityColor:"FFFFFF",citySize:"16",aqiColor:"E8D87B",aqiSize:"16",weatherIconSize:"24",alertIconSize:"18",padding:"10px 10px 10px 10px",shadow:"0",language:"auto",borderRadius:"20",fixed:"true",vertical:"top",horizontal:"left",left:"20",top:"7.1",key:"df245676fb434a0691ead1c63341cd94"}}</script><link rel="stylesheet" href="https://widget.qweather.net/simple/static/css/he-simple.css?v=1.4.0"><script src="https://widget.qweather.net/simple/static/js/he-simple.js?v=1.4.0"></script><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.umd.js"></script><script src="https://cdn.cbd.int/instant.page@5.2.0/instantpage.js" type="module"></script><script src="https://cdn.cbd.int/vanilla-lazyload@17.8.4/dist/lazyload.iife.min.js"></script><script src="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.js"></script><script>function panguFn(){"object"==typeof pangu?pangu.autoSpacingPage():getScript("https://cdn.cbd.int/pangu@4.0.7/dist/browser/pangu.min.js").then(()=>{pangu.autoSpacingPage()})}function panguInit(){panguFn()}document.addEventListener("DOMContentLoaded",panguInit)</script><script>var meting_api="https://api.injahow.cn/meting/?server=:server&type=:type&id=:id&auth=:auth&r=:r"</script><canvas id="universe"></canvas><script async src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/dark/dark.js"></script><script>var HoldLog=console.log;console.log=function(){};let now1=new Date;queueMicrotask(()=>{function o(){HoldLog.apply(console,arguments)}var c=new Date("09/01/2018 00:00:00"),c=(now1.setTime(now1.getTime()+250),(now1-c)/1e3/60/60/24),c=["欢迎使用安知鱼!","生活明朗, 万物可爱",`
        
       █████╗ ███╗   ██╗███████╗██╗  ██╗██╗██╗   ██╗██╗   ██╗
      ██╔══██╗████╗  ██║╚══███╔╝██║  ██║██║╚██╗ ██╔╝██║   ██║
      ███████║██╔██╗ ██║  ███╔╝ ███████║██║ ╚████╔╝ ██║   ██║
      ██╔══██║██║╚██╗██║ ███╔╝  ██╔══██║██║  ╚██╔╝  ██║   ██║
      ██║  ██║██║ ╚████║███████╗██║  ██║██║   ██║   ╚██████╔╝
      ╚═╝  ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝    ╚═════╝
        
        `,"已上线",Math.floor(c),"天","©2018 By 安知鱼 V1.6.6"],e=["NCC2-036","调用前置摄像头拍照成功，识别为【小笨蛋】.","Photo captured: ","🤪"];setTimeout(o.bind(console,`
%c${c[0]} %c ${c[1]} %c ${c[2]} %c${c[3]}%c ${c[4]}%c ${c[5]}

%c ${c[6]}
`,"color:#425AEF","","color:#425AEF","color:#425AEF","","color:#425AEF","")),setTimeout(o.bind(console,`%c ${e[0]} %c ${e[1]} %c 
${e[2]} %c
${e[3]}
`,"color:white; background-color:#4fd953","","",'background:url("https://npm.elemecdn.com/anzhiyu-blog@1.1.6/img/post/common/tinggge.gif") no-repeat;font-size:450%')),setTimeout(o.bind(console,"%c WELCOME %c 你好，小笨蛋.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c ⚡ Powered by 安知鱼 %c 你正在访问 云少 的博客.","color:white; background-color:#f0ad4e","")),setTimeout(o.bind(console,"%c W23-12 %c 你已打开控制台.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c S013-782 %c 你现在正处于监控中.","color:white; background-color:#d9534f",""))})</script><script async src="/anzhiyu/random.js"></script><script async>!function(){var n,o,r,a,i,e=new Date("09/01/2018 00:00:00"),l=new Date;setInterval(()=>{var t;if(l=new Date,i=l.getHours(),t=(l-e)/1e3/60/60/24,n=Math.floor(t),t=(l-e)/1e3/60/60-24*n,o=Math.floor(t),1==String(o).length&&(o="0"+o),t=(l-e)/1e3/60-1440*n-60*o,r=Math.floor(t),1==String(r).length&&(r="0"+r),t=(l-e)/1e3-86400*n-3600*o-60*r,a=Math.round(t),1==String(a).length&&(a="0"+a),document.getElementById("footer")){let e="";e=(i<18&&9<=i||((t=document.querySelector("#workboard .workSituationImg")).src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-下班啦.svg",t.title="下班了就该开开心心的玩耍，嘿嘿~",t.alt="下班了就该开开心心的玩耍，嘿嘿~"),`本站居然运行了 ${n} 天<span id='runtime'> ${o} 小时 ${r} 分 ${a} 秒 </span><i class='anzhiyufont anzhiyu-icon-heartbeat' style='color:red'></i>`),document.getElementById("runtimeTextTip")&&(document.getElementById("runtimeTextTip").innerHTML=e)}},1e3)}()</script><script src="https://cdn.cbd.int/algoliasearch@4.18.0/dist/algoliasearch-lite.umd.js"></script><script src="https://cdn.cbd.int/instantsearch.js@4.56.5/dist/instantsearch.production.min.js"></script><script src="/js/search/algolia.js"></script><div class="js-pjax"><script>(()=>{const e=document.querySelectorAll("#article-container .mermaid-wrap");if(0!==e.length){const n=()=>{window.loadMermaid=!0;const a="dark"===document.documentElement.getAttribute("data-theme")?"dark":"default";Array.from(e).forEach((e,t)=>{const n=e.firstElementChild;e="%%{init:{ 'theme':'"+a+"'}}%%\n"+n.textContent;const d=mermaid.render("mermaid-"+t,e);"string"==typeof d?(t=d,n.insertAdjacentHTML("afterend",t)):d.then(({svg:e})=>{n.insertAdjacentHTML("afterend",e)})})};var t=()=>{window.loadMermaid?n():getScript("https://cdn.cbd.int/mermaid@10.2.4/dist/mermaid.min.js").then(n)};anzhiyu.addGlobalFn("themeChange",n,"mermaid"),window.pjax?t():document.addEventListener("DOMContentLoaded",t)}})()</script><script>(()=>{const t=()=>{twikoo.init(Object.assign({el:"#twikoo-wrap",envId:"https://twikoo.tryrun.top/",region:"",onCommentLoaded:()=>{anzhiyu.loadLightbox(document.querySelectorAll("#twikoo .tk-content img:not(.tk-owo-emotion)"))}},null))};var o=()=>{"object"==typeof twikoo?setTimeout(n,0):getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(n)};const n=()=>{t()};anzhiyu.loadComment(document.getElementById("twikoo-wrap"),o)})()</script><script>(()=>{const t=()=>{window.artalkItem=new Artalk(Object.assign({el:"#artalk-wrap",server:"https://artalk.tryrun.top/",site:"https://blog.tryrun.top/",pageKey:location.pathname,darkMode:"dark"===document.documentElement.getAttribute("data-theme"),countEl:".artalk-count"},null)),"null"!==GLOBAL_CONFIG.lightbox&&window.artalkItem.use(t=>{t.on("list-loaded",()=>{t.getCommentList().forEach(t=>{t=t.getRender().$content;anzhiyu.loadLightbox(t.querySelectorAll("img:not([atk-emoticon])"))})})})};var a=async()=>{"object"==typeof window.artalkItem||(await getCSS("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.css"),await getScript("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.js")),t()};anzhiyu.addGlobalFn("themeChange",t=>{var a=document.getElementById("artalk-wrap");a&&a.children.length&&window.artalkItem.setDarkMode("dark"===t)},"artalk"),window.loadOtherComment=a})()</script><input type="hidden" name="page-type" id="page-type" value="post"></div><script>window.addEventListener("load",()=>{const t=e=>e=""!==e&&150<(e=(e=(e=(e=e.replace(/<img.*?src="(.*?)"?[^\>]+>/gi,"[图片]")).replace(/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi,"[链接]")).replace(/<pre><code>.*?<\/pre>/gi,"[代码]")).replace(/<[^>]+>/g,"")).length?e.substring(0,150)+"...":e,n=t=>{let n="";if(t.length)for(let e=0;e<t.length;e++)n=(n=(n+="<div class='aside-list-item'>")+`<a href='${t[e].url}' class='thumbnail'><img data-lazy-src='${t[e].avatar}' alt='${t[e].nick}'><div class='name'><span>${t[e].nick} </span></div></a>`)+`<div class='content'>
        <a class='comment' href='${t[e].url}' title='${t[e].content}'>${t[e].content}</a>
        <time datetime="${t[e].date}">${anzhiyu.diffDate(t[e].date,!0)}</time></div>
        </div>`;else n+="没有评论";var e=document.querySelector("#card-newest-comments .aside-list");e.innerHTML=n,window.lazyLoadInstance&&window.lazyLoadInstance.update(),window.pjax&&window.pjax.refresh(e)};var e=()=>{var e;document.querySelector("#card-newest-comments .aside-list")&&((e=saveToLocal.get("twikoo-newest-comments"))?n(JSON.parse(e)):(e=()=>{twikoo.getRecentComments({envId:"https://twikoo.tryrun.top/",region:"",pageSize:6,includeReply:!0}).then(function(e){e=e.map(e=>({content:t(e.comment),avatar:e.avatar,nick:e.nick,url:e.url+"#"+e.id,date:new Date(e.created).toISOString()}));saveToLocal.set("twikoo-newest-comments",JSON.stringify(e),10/1440),n(e)}).catch(function(e){document.querySelector("#card-newest-comments .aside-list").textContent="无法获取评论，请确认相关配置是否正确"})},"object"==typeof twikoo?e():getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(e)))};e(),document.addEventListener("pjax:complete",e)})</script><script async data-pjax src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.1/bubble/bubble.js"></script><script>var visitorMail="visitor@anheyu.com"</script><script async data-pjax src="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/waterfall/waterfall.js"></script><script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/qrcodejs/1.0.0/qrcode.min.js"></script><script src="/js/anzhiyu/right_click_menu.js"></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.9/icon/ali_iconfont_css.css"><script async src="/js/1.min.js?1"></script><script async src="/js/bg.js?1"></script><script async src="https://cdn1.tianli0.top/npm/sweetalert2@8.19.0/dist/sweetalert2.all.js"></script><script async src="/js/lunar.min.js?1"></script><script async src="/js/day.min.js?1"></script><script async src="/js/winbox.bundle.min.js?1"></script><script id="click-show-text" src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/dist/click-show-text.min.js" data-mobile="true" data-text="富强,民主,文明,和谐,自由,平等,公正,法治,爱国,敬业,诚信,友善" data-fontsize="15px" data-random="true" async></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/aplayer/APlayer.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/anzhiyu-blog-static@1.0.1/js/APlayer.min.js"></script><script src="https://cdn.cbd.int/hexo-anzhiyu-music@1.0.1/assets/js/Meting2.min.js"></script><script src="https://cdn.cbd.int/pjax@0.2.8/pjax.min.js"></script><script>let pjaxSelectors=['meta[property="og:image"]','meta[property="og:title"]','meta[property="og:url"]','meta[property="og:type"]','meta[property="og:site_name"]','meta[property="og:description"]',"head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"];var pjax=new Pjax({elements:'a:not([target="_blank"])',selectors:pjaxSelectors,cacheBust:!1,analytics:!0,scrollRestoration:!1});document.addEventListener("pjax:send",function(){if(anzhiyu.removeGlobalFnEvent("pjax"),anzhiyu.removeGlobalFnEvent("themeChange"),document.getElementById("rightside").classList.remove("rightside-show"),window.aplayers)for(let e=0;e<window.aplayers.length;e++)window.aplayers[e].options.fixed||window.aplayers[e].destroy();"object"==typeof typed&&typed.destroy();var e=document.body.classList;e.contains("read-mode")&&e.remove("read-mode")}),document.addEventListener("pjax:complete",function(){window.refreshFn(),document.querySelectorAll("script[data-pjax]").forEach(e=>{const t=document.createElement("script");var a=e.text||e.textContent||e.innerHTML||"";Array.from(e.attributes).forEach(e=>t.setAttribute(e.name,e.value)),t.appendChild(document.createTextNode(a)),e.parentNode.replaceChild(t,e)}),GLOBAL_CONFIG.islazyload&&window.lazyLoadInstance.update(),"function"==typeof chatBtnFn&&chatBtnFn(),"function"==typeof panguInit&&panguInit(),"function"==typeof gtag&&gtag("config","G-3VMKW5TZBM",{page_path:window.location.pathname}),"object"==typeof _hmt&&_hmt.push(["_trackPageview",window.location.pathname]),"function"==typeof loadMeting&&document.getElementsByClassName("aplayer").length&&loadMeting(),"object"==typeof Prism&&Prism.highlightAll()}),document.addEventListener("pjax:error",e=>{404===e.request.status&&pjax.loadUrl("/404.html")})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script charset="UTF-8" src="https://cdn.cbd.int/anzhiyu-theme-static@1.1.5/accesskey/accesskey.js"></script></div><div id="popup-window"><div class="popup-window-title">通知</div><div class="popup-window-divider"></div><div class="popup-window-content"><div class="popup-tip">你好呀</div><div class="popup-link"><i class="anzhiyufont anzhiyu-icon-arrow-circle-right"></i></div></div></div></body></html>